From c0aaa8fcea6a9524c7fe0de08c7b373f2aba62d6 Mon Sep 17 00:00:00 2001
From: "Adam J. Stewart" <ajstewart426@gmail.com>
Date: Mon, 16 Jan 2017 18:13:37 -0800
Subject: [PATCH] Add PythonPackage base class

- Add a PythonPackage class with build system support.
  - Support build phases in PythonPackage
  - Add a custom sanity check for PythonPackages
  - Get rid of nolink dependencies in python packages

- Update spack create to use new PythonPackage class

- Port most of Python packages to new PythonPackage class

- Conducted a massive install and activate of Python packages.
  - Fixed bugs introduced by install and activate.

- Update API docs on PythonPackage
---
 lib/spack/spack/__init__.py                   |   3 +-
 lib/spack/spack/build_systems/python.py       | 309 ++++++++++++++++++
 lib/spack/spack/cmd/build.py                  |   3 +-
 lib/spack/spack/cmd/create.py                 |  15 +-
 lib/spack/spack/directives.py                 |  12 +-
 .../repos/builtin/packages/py-3to2/package.py |   7 +-
 .../builtin/packages/py-alabaster/package.py  |   7 +-
 .../packages/py-argcomplete/package.py        |   6 +-
 .../builtin/packages/py-astroid/package.py    |   9 +-
 .../builtin/packages/py-astropy/package.py    |   8 +-
 .../builtin/packages/py-autopep8/package.py   |   5 +-
 .../builtin/packages/py-babel/package.py      |   7 +-
 .../packages/py-backports-abc/package.py      |   7 +-
 .../package.py                                |   9 +-
 .../package.py                                |   7 +-
 .../builtin/packages/py-basemap/package.py    |  13 +-
 .../packages/py-beautifulsoup4/package.py     |   7 +-
 .../builtin/packages/py-biopython/package.py  |   6 +-
 .../builtin/packages/py-bleach/package.py     |  39 +++
 .../builtin/packages/py-blessings/package.py  |   7 +-
 .../builtin/packages/py-bottleneck/package.py |   6 +-
 .../builtin/packages/py-cclib/package.py      |  11 +-
 .../repos/builtin/packages/py-cdo/package.py  |   9 +-
 .../builtin/packages/py-certifi/package.py    |   7 +-
 .../repos/builtin/packages/py-cffi/package.py |  11 +-
 .../packages/py-configparser/package.py       |  10 +-
 .../builtin/packages/py-coverage/package.py   |   7 +-
 .../builtin/packages/py-csvkit/package.py     |   7 +-
 .../builtin/packages/py-cycler/package.py     |   7 +-
 .../builtin/packages/py-cython/package.py     |   9 +-
 .../repos/builtin/packages/py-dask/package.py |   7 +-
 .../builtin/packages/py-dateutil/package.py   |   6 +-
 .../repos/builtin/packages/py-dbf/package.py  |   7 +-
 .../builtin/packages/py-decorator/package.py  |   7 +-
 .../builtin/packages/py-docutils/package.py   |  11 +-
 .../builtin/packages/py-emcee/package.py      |   6 +-
 .../builtin/packages/py-enum34/package.py     |  10 +-
 .../builtin/packages/py-epydoc/package.py     |   7 +-
 .../builtin/packages/py-flake8/package.py     |  15 +-
 .../builtin/packages/py-funcsigs/package.py   |   7 +-
 .../packages/py-functools32/package.py        |   8 +-
 .../builtin/packages/py-futures/package.py    |   7 +-
 .../builtin/packages/py-genders/package.py    |   3 +
 .../builtin/packages/py-genshi/package.py     |   9 +-
 .../builtin/packages/py-gnuplot/package.py    |   6 +-
 .../repos/builtin/packages/py-h5py/package.py |  14 +-
 .../builtin/packages/py-html5lib/package.py   |  37 +++
 .../builtin/packages/py-imagesize/package.py  |   7 +-
 .../builtin/packages/py-iminuit/package.py    |   6 +-
 .../builtin/packages/py-ipykernel/package.py  |  17 +-
 .../packages/py-ipython-genutils/package.py   |   7 +-
 .../builtin/packages/py-ipython/package.py    |  18 +-
 .../builtin/packages/py-ipywidgets/package.py |  13 +-
 .../builtin/packages/py-jdcal/package.py      |   7 +-
 .../builtin/packages/py-jinja2/package.py     |   7 +-
 .../builtin/packages/py-jsonschema/package.py |  13 +-
 .../packages/py-jupyter-client/package.py     |  13 +-
 .../packages/py-jupyter-console/package.py    |  17 +-
 .../packages/py-jupyter-core/package.py       |  11 +-
 .../packages/py-jupyter-notebook/package.py   |  31 +-
 .../builtin/packages/py-lockfile/package.py   |   6 +-
 .../packages/py-logilab-common/package.py     |   8 +-
 .../builtin/packages/py-macs2/package.py      |   6 +-
 .../repos/builtin/packages/py-mako/package.py |   7 +-
 .../builtin/packages/py-markdown/package.py   |   7 +-
 .../builtin/packages/py-markupsafe/package.py |   7 +-
 .../builtin/packages/py-matplotlib/package.py |  11 +-
 .../builtin/packages/py-mccabe/package.py     |   6 +-
 .../repos/builtin/packages/py-meep/package.py |  30 +-
 .../builtin/packages/py-mistune/package.py    |   9 +-
 .../repos/builtin/packages/py-mock/package.py |   6 +-
 .../builtin/packages/py-monotonic/package.py  |   7 +-
 .../builtin/packages/py-mpi4py/package.py     |   6 +-
 .../builtin/packages/py-mpmath/package.py     |   7 +-
 .../repos/builtin/packages/py-mx/package.py   |   7 +-
 .../builtin/packages/py-mysqldb1/package.py   |  11 +-
 .../builtin/packages/py-nbconvert/package.py  |  35 +-
 .../builtin/packages/py-nbformat/package.py   |  15 +-
 .../builtin/packages/py-nestle/package.py     |   6 +-
 .../builtin/packages/py-netcdf/package.py     |   6 +-
 .../builtin/packages/py-networkx/package.py   |   7 +-
 .../repos/builtin/packages/py-nose/package.py |   6 +-
 .../builtin/packages/py-numexpr/package.py    |  14 +-
 .../builtin/packages/py-numpy/package.py      |   8 +-
 .../builtin/packages/py-openpyxl/package.py   |   7 +-
 .../packages/py-ordereddict/package.py        |   8 +-
 .../builtin/packages/py-pandas/package.py     |   6 +-
 .../builtin/packages/py-pathlib2/package.py   |   7 +-
 .../builtin/packages/py-pathspec/package.py   |   6 +-
 .../repos/builtin/packages/py-pbr/package.py  |   7 +-
 .../packages/py-periodictable/package.py      |   6 +-
 .../builtin/packages/py-pexpect/package.py    |  10 +-
 .../builtin/packages/py-phonopy/package.py    |   6 +-
 .../packages/py-pickleshare/package.py        |   7 +-
 .../repos/builtin/packages/py-pil/package.py  |   6 +-
 .../builtin/packages/py-pillow/package.py     |  15 +-
 .../repos/builtin/packages/py-pip/package.py  |   7 +-
 .../repos/builtin/packages/py-ply/package.py  |   7 +-
 .../repos/builtin/packages/py-pmw/package.py  |   7 +-
 .../packages/py-prettytable/package.py        |   6 +-
 .../repos/builtin/packages/py-proj/package.py |   7 +-
 .../packages/py-prompt-toolkit/package.py     |  11 +-
 .../builtin/packages/py-protobuf/package.py   |   7 +-
 .../builtin/packages/py-ptyprocess/package.py |   7 +-
 .../repos/builtin/packages/py-pudb/package.py |   7 +-
 .../repos/builtin/packages/py-py/package.py   |   7 +-
 .../builtin/packages/py-py2cairo/package.py   |  12 +-
 .../builtin/packages/py-py2neo/package.py     |   9 +-
 .../builtin/packages/py-pychecker/package.py  |   9 +-
 .../packages/py-pycodestyle/package.py        |   7 +-
 .../builtin/packages/py-pycparser/package.py  |   6 +-
 .../builtin/packages/py-pycurl/package.py     |   6 +-
 .../builtin/packages/py-pydatalog/package.py  |   7 +-
 .../builtin/packages/py-pyelftools/package.py |   7 +-
 .../builtin/packages/py-pyflakes/package.py   |   9 +-
 .../builtin/packages/py-pygments/package.py   |   7 +-
 .../builtin/packages/py-pygobject/package.py  |   2 +
 .../builtin/packages/py-pylint/package.py     |   5 +-
 .../builtin/packages/py-pypar/package.py      |  12 +-
 .../builtin/packages/py-pyparsing/package.py  |   7 +-
 .../builtin/packages/py-pyside/package.py     |   7 +-
 .../builtin/packages/py-pytables/package.py   |  15 +-
 .../builtin/packages/py-pytest/package.py     |   7 +-
 .../packages/py-python-daemon/package.py      |   6 +-
 .../repos/builtin/packages/py-pytz/package.py |   7 +-
 .../builtin/packages/py-pyyaml/package.py     |   7 +-
 .../packages/py-readme-renderer/package.py    |  42 +++
 .../builtin/packages/py-restview/package.py   |  10 +-
 .../repos/builtin/packages/py-rpy2/package.py |  12 +-
 .../builtin/packages/py-rtree/package.py      |  19 +-
 .../packages/py-scientificpython/package.py   |   7 +-
 .../packages/py-scikit-image/package.py       |   5 +-
 .../packages/py-scikit-learn/package.py       |   9 +-
 .../builtin/packages/py-scipy/package.py      |  11 +-
 .../builtin/packages/py-setuptools/package.py |   7 +-
 .../builtin/packages/py-shiboken/package.py   |   8 +-
 .../packages/py-simplegeneric/package.py      |   9 +-
 .../packages/py-singledispatch/package.py     |  10 +-
 .../repos/builtin/packages/py-six/package.py  |   5 +-
 .../builtin/packages/py-sncosmo/package.py    |   5 +-
 .../packages/py-snowballstemmer/package.py    |   7 +-
 .../packages/py-sphinx-rtd-theme/package.py   |   7 +-
 .../builtin/packages/py-sphinx/package.py     |   5 +-
 .../builtin/packages/py-sqlalchemy/package.py |   7 +-
 .../builtin/packages/py-storm/package.py      |   9 +-
 .../builtin/packages/py-symengine/package.py  |   8 +-
 .../builtin/packages/py-sympy/package.py      |   6 +-
 .../builtin/packages/py-tappy/package.py      |  11 +-
 .../builtin/packages/py-terminado/package.py  |  11 +-
 .../builtin/packages/py-tornado/package.py    |  27 +-
 .../builtin/packages/py-traitlets/package.py  |  13 +-
 .../builtin/packages/py-tuiview/package.py    |   6 +-
 .../builtin/packages/py-twisted/package.py    |   7 +-
 .../builtin/packages/py-unittest2/package.py  |   6 +-
 .../packages/py-unittest2py3k/package.py      |   7 +-
 .../builtin/packages/py-urwid/package.py      |   7 +-
 .../packages/py-vcversioner/package.py        |   7 +-
 .../builtin/packages/py-virtualenv/package.py |   6 +-
 .../builtin/packages/py-wcsaxes/package.py    |   7 +-
 .../builtin/packages/py-wcwidth/package.py    |   7 +-
 .../builtin/packages/py-wheel/package.py      |   6 +-
 .../packages/py-widgetsnbextension/package.py |   9 +-
 .../repos/builtin/packages/py-xlrd/package.py |   7 +-
 .../repos/builtin/packages/py-yapf/package.py |   6 +-
 .../repos/builtin/packages/py-yt/package.py   |  13 +-
 .../repos/builtin/packages/py-zmq/package.py  |  13 +-
 .../repos/builtin/packages/python/package.py  |   5 +
 .../repos/builtin/packages/scons/package.py   |   7 +-
 .../builtin/packages/udunits2/package.py      |   4 +-
 169 files changed, 849 insertions(+), 1011 deletions(-)
 create mode 100644 lib/spack/spack/build_systems/python.py
 create mode 100644 var/spack/repos/builtin/packages/py-bleach/package.py
 create mode 100644 var/spack/repos/builtin/packages/py-html5lib/package.py
 create mode 100644 var/spack/repos/builtin/packages/py-readme-renderer/package.py

diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 901b8f115c..1f8926e28d 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -159,9 +159,10 @@
 from spack.build_systems.makefile import MakefilePackage
 from spack.build_systems.autotools import AutotoolsPackage
 from spack.build_systems.cmake import CMakePackage
+from spack.build_systems.python import PythonPackage
 from spack.build_systems.r import RPackage
 __all__ += ['Package', 'CMakePackage', 'AutotoolsPackage', 'MakefilePackage',
-            'RPackage']
+            'PythonPackage', 'RPackage']
 
 from spack.version import Version, ver
 __all__ += ['Version', 'ver']
diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py
new file mode 100644
index 0000000000..d21c291ae6
--- /dev/null
+++ b/lib/spack/spack/build_systems/python.py
@@ -0,0 +1,309 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+
+import inspect
+
+from spack.directives import extends
+from spack.package import PackageBase
+
+from llnl.util.filesystem import working_dir
+
+
+class PythonPackage(PackageBase):
+    """Specialized class for packages that are built using Python
+    setup.py files
+
+    This class provides the following phases that can be overridden:
+
+    * build
+    * build_py
+    * build_ext
+    * build_clib
+    * build_scripts
+    * clean
+    * install
+    * install_lib
+    * install_headers
+    * install_scripts
+    * install_data
+    * sdist
+    * register
+    * bdist
+    * bdist_dumb
+    * bdist_rpm
+    * bdist_wininst
+    * upload
+    * check
+
+    These are all standard setup.py commands and can be found by running:
+
+    .. code-block:: console
+
+       $ python setup.py --help-commands
+
+    By default, only the 'build' and 'install' phases are run, but if you
+    need to run more phases, simply modify your ``phases`` list like so:
+
+    .. code-block:: python
+
+       phases = ['build_ext', 'install', 'bdist']
+
+    Each phase provides a function <phase> that runs:
+
+    .. code-block:: console
+
+       $ python --no-user-cfg setup.py <phase>
+
+    Each phase also has a <phase_args> function that can pass arguments to
+    this call. All of these functions are empty except for the ``install_args``
+    function, which passes ``--prefix=/path/to/installation/directory``.
+
+    If you need to run a phase which is not a standard setup.py command,
+    you'll need to define a function for it like so:
+
+    .. code-block:: python
+
+       def configure(self, spec, prefix):
+           self.setup_py('configure')
+    """
+    # Default phases
+    phases = ['build', 'install']
+
+    # To be used in UI queries that require to know which
+    # build-system class we are using
+    build_system_class = 'PythonPackage'
+
+    extends('python')
+
+    def setup_file(self, spec, prefix):
+        """Returns the name of the setup file to use."""
+        return 'setup.py'
+
+    def build_directory(self):
+        """The directory containing the ``setup.py`` file."""
+        return self.stage.source_path
+
+    def python(self, *args):
+        inspect.getmodule(self).python(*args)
+
+    def setup_py(self, *args):
+        setup = self.setup_file(self.spec, self.prefix)
+
+        with working_dir(self.build_directory()):
+            self.python(setup, '--no-user-cfg', *args)
+
+    # The following phases and their descriptions come from:
+    #   $ python setup.py --help-commands
+    # Only standard commands are included here, but some packages
+    # define extra commands as well
+
+    def build(self, spec, prefix):
+        """Build everything needed to install."""
+        args = self.build_args(spec, prefix)
+
+        self.setup_py('build', *args)
+
+    def build_args(self, spec, prefix):
+        """Arguments to pass to build."""
+        return []
+
+    def build_py(self, spec, prefix):
+        '''"Build" pure Python modules (copy to build directory).'''
+        args = self.build_py_args(spec, prefix)
+
+        self.setup_py('build_py', *args)
+
+    def build_py_args(self, spec, prefix):
+        """Arguments to pass to build_py."""
+        return []
+
+    def build_ext(self, spec, prefix):
+        """Build C/C++ extensions (compile/link to build directory)."""
+        args = self.build_ext_args(spec, prefix)
+
+        self.setup_py('build_ext', *args)
+
+    def build_ext_args(self, spec, prefix):
+        """Arguments to pass to build_ext."""
+        return []
+
+    def build_clib(self, spec, prefix):
+        """Build C/C++ libraries used by Python extensions."""
+        args = self.build_clib_args(spec, prefix)
+
+        self.setup_py('build_clib', *args)
+
+    def build_clib_args(self, spec, prefix):
+        """Arguments to pass to build_clib."""
+        return []
+
+    def build_scripts(self, spec, prefix):
+        '''"Build" scripts (copy and fixup #! line).'''
+        args = self.build_scripts_args(spec, prefix)
+
+        self.setup_py('build_scripts', *args)
+
+    def clean(self, spec, prefix):
+        """Clean up temporary files from 'build' command."""
+        args = self.clean_args(spec, prefix)
+
+        self.setup_py('clean', *args)
+
+    def clean_args(self, spec, prefix):
+        """Arguments to pass to clean."""
+        return []
+
+    def install(self, spec, prefix):
+        """Install everything from build directory."""
+        args = self.install_args(spec, prefix)
+
+        self.setup_py('install', *args)
+
+    def install_args(self, spec, prefix):
+        """Arguments to pass to install."""
+        return ['--prefix={0}'.format(prefix)]
+
+    def install_lib(self, spec, prefix):
+        """Install all Python modules (extensions and pure Python)."""
+        args = self.install_lib_args(spec, prefix)
+
+        self.setup_py('install_lib', *args)
+
+    def install_lib_args(self, spec, prefix):
+        """Arguments to pass to install_lib."""
+        return []
+
+    def install_headers(self, spec, prefix):
+        """Install C/C++ header files."""
+        args = self.install_headers_args(spec, prefix)
+
+        self.setup_py('install_headers', *args)
+
+    def install_headers_args(self, spec, prefix):
+        """Arguments to pass to install_headers."""
+        return []
+
+    def install_scripts(self, spec, prefix):
+        """Install scripts (Python or otherwise)."""
+        args = self.install_scripts_args(spec, prefix)
+
+        self.setup_py('install_scripts', *args)
+
+    def install_scripts_args(self, spec, prefix):
+        """Arguments to pass to install_scripts."""
+        return []
+
+    def install_data(self, spec, prefix):
+        """Install data files."""
+        args = self.install_data_args(spec, prefix)
+
+        self.setup_py('install_data', *args)
+
+    def install_data_args(self, spec, prefix):
+        """Arguments to pass to install_data."""
+        return []
+
+    def sdist(self, spec, prefix):
+        """Create a source distribution (tarball, zip file, etc.)."""
+        args = self.sdist_args(spec, prefix)
+
+        self.setup_py('sdist', *args)
+
+    def sdist_args(self, spec, prefix):
+        """Arguments to pass to sdist."""
+        return []
+
+    def register(self, spec, prefix):
+        """Register the distribution with the Python package index."""
+        args = self.register_args(spec, prefix)
+
+        self.setup_py('register', *args)
+
+    def register_args(self, spec, prefix):
+        """Arguments to pass to register."""
+        return []
+
+    def bdist(self, spec, prefix):
+        """Create a built (binary) distribution."""
+        args = self.bdist_args(spec, prefix)
+
+        self.setup_py('bdist', *args)
+
+    def bdist_args(self, spec, prefix):
+        """Arguments to pass to bdist."""
+        return []
+
+    def bdist_dumb(self, spec, prefix):
+        '''Create a "dumb" built distribution.'''
+        args = self.bdist_dumb_args(spec, prefix)
+
+        self.setup_py('bdist_dumb', *args)
+
+    def bdist_dumb_args(self, spec, prefix):
+        """Arguments to pass to bdist_dumb."""
+        return []
+
+    def bdist_rpm(self, spec, prefix):
+        """Create an RPM distribution."""
+        args = self.bdist_rpm(spec, prefix)
+
+        self.setup_py('bdist_rpm', *args)
+
+    def bdist_rpm_args(self, spec, prefix):
+        """Arguments to pass to bdist_rpm."""
+        return []
+
+    def bdist_wininst(self, spec, prefix):
+        """Create an executable installer for MS Windows."""
+        args = self.bdist_wininst_args(spec, prefix)
+
+        self.setup_py('bdist_wininst', *args)
+
+    def bdist_wininst_args(self, spec, prefix):
+        """Arguments to pass to bdist_wininst."""
+        return []
+
+    def upload(self, spec, prefix):
+        """Upload binary package to PyPI."""
+        args = self.upload_args(spec, prefix)
+
+        self.setup_py('upload', *args)
+
+    def upload_args(self, spec, prefix):
+        """Arguments to pass to upload."""
+        return []
+
+    def check(self, spec, prefix):
+        """Perform some checks on the package."""
+        args = self.check_args(spec, prefix)
+
+        self.setup_py('check', *args)
+
+    def check_args(self, spec, prefix):
+        """Arguments to pass to check."""
+        return []
+
+    # Check that self.prefix is there after installation
+    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py
index 703705a32c..6c0029794f 100644
--- a/lib/spack/spack/cmd/build.py
+++ b/lib/spack/spack/cmd/build.py
@@ -30,7 +30,8 @@
 
 build_system_to_phase = {
     CMakePackage: 'build',
-    AutotoolsPackage: 'build'
+    AutotoolsPackage: 'build',
+    PythonPackage: 'build'
 }
 
 
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index a488104282..2575229581 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -194,19 +194,20 @@ def install(self, spec, prefix):
 
 
 class PythonPackageTemplate(PackageTemplate):
-    """Provides appropriate overrides for Python extensions"""
+    """Provides appropriate overrides for python extensions"""
+    base_class_name = 'PythonPackage'
 
     dependencies = """\
-    extends('python')
-
-    # FIXME: Add additional dependencies if required.
+    # FIXME: Add dependencies if required.
     # depends_on('py-setuptools', type='build')
     # depends_on('py-foo',        type=('build', 'run'))"""
 
     body = """\
-    def install(self, spec, prefix):
-        # FIXME: Add logic to build and install here.
-        setup_py('install', '--prefix={0}'.format(prefix))"""
+    def build_args(self):
+        # FIXME: Add arguments other than --prefix
+        # FIXME: If not needed delete the function
+        args = []
+        return args"""
 
     def __init__(self, name, *args):
         # If the user provided `--name py-numpy`, don't rename it py-py-numpy
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 72656684e0..58eabb9e3b 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -289,9 +289,10 @@ def extends(spec, **kwargs):
 
     """
     def _execute(pkg):
-        if pkg.extendees:
-            msg = 'Packages can extend at most one other package.'
-            raise DirectiveError(msg)
+        # if pkg.extendees:
+        #     directive = 'extends'
+        #     msg = 'Packages can extend at most one other package.'
+        #     raise DirectiveError(directive, msg)
 
         when = kwargs.pop('when', pkg.name)
         _depends_on(pkg, spec, when=when)
@@ -344,8 +345,9 @@ def variant(name, default=False, description=""):
 
     def _execute(pkg):
         if not re.match(spack.spec.identifier_re, name):
-            msg = 'Invalid variant name in {0}: \'{1}\''
-            raise DirectiveError(msg.format(pkg.name, name))
+            directive = 'variant'
+            msg = "Invalid variant name in {0}: '{1}'"
+            raise DirectiveError(directive, msg.format(pkg.name, name))
 
         pkg.variants[name] = Variant(default, description)
     return _execute
diff --git a/var/spack/repos/builtin/packages/py-3to2/package.py b/var/spack/repos/builtin/packages/py-3to2/package.py
index d0b6857aaf..80b95fcbfd 100644
--- a/var/spack/repos/builtin/packages/py-3to2/package.py
+++ b/var/spack/repos/builtin/packages/py-3to2/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class Py3to2(Package):
+class Py3to2(PythonPackage):
     """lib3to2 is a set of fixers that are intended to backport code written
     for Python version 3.x into Python version 2.x."""
 
@@ -33,8 +33,3 @@ class Py3to2(Package):
     url      = "https://pypi.python.org/packages/source/3/3to2/3to2-1.1.1.zip"
 
     version('1.1.1', 'cbeed28e350dbdaef86111ace3052824')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-alabaster/package.py b/var/spack/repos/builtin/packages/py-alabaster/package.py
index 24cb5dce22..f2402c9bc6 100644
--- a/var/spack/repos/builtin/packages/py-alabaster/package.py
+++ b/var/spack/repos/builtin/packages/py-alabaster/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyAlabaster(Package):
+class PyAlabaster(PythonPackage):
     """Alabaster is a visually (c)lean, responsive, configurable theme
     for the Sphinx documentation system."""
 
@@ -35,9 +35,4 @@ class PyAlabaster(Package):
     version('0.7.9', 'b29646a8bbe7aa52830375b7d17b5d7a',
             url="https://pypi.python.org/packages/71/c3/70da7d8ac18a4f4c502887bd2549e05745fa403e2cd9d06a8a9910a762bc/alabaster-0.7.9.tar.gz")
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-argcomplete/package.py b/var/spack/repos/builtin/packages/py-argcomplete/package.py
index fe948f8061..585540f23b 100644
--- a/var/spack/repos/builtin/packages/py-argcomplete/package.py
+++ b/var/spack/repos/builtin/packages/py-argcomplete/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyArgcomplete(Package):
+class PyArgcomplete(PythonPackage):
     """Bash tab completion for argparse."""
 
     homepage = "https://pypi.python.org/pypi/argcomplete"
@@ -34,7 +34,3 @@ class PyArgcomplete(Package):
     version('1.1.1', '89a3839096c9f991ad33828e72d21abf')
 
     depends_on('py-setuptools', type='build')
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-astroid/package.py b/var/spack/repos/builtin/packages/py-astroid/package.py
index a42d936f05..f275813d86 100644
--- a/var/spack/repos/builtin/packages/py-astroid/package.py
+++ b/var/spack/repos/builtin/packages/py-astroid/package.py
@@ -22,11 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import depends_on, extends, version
-from spack import Package
+from spack import *
 
 
-class PyAstroid(Package):
+class PyAstroid(PythonPackage):
     homepage = "https://www.astroid.org/"
     url      = "https://github.com/PyCQA/astroid/archive/astroid-1.4.5.tar.gz"
 
@@ -36,10 +35,6 @@ class PyAstroid(Package):
     version('1.4.2', '677f7965840f375af51b0e86403bee6a')
     version('1.4.1', 'ed70bfed5e4b25be4292e7fe72da2c02')
 
-    extends('python')
     depends_on('py-logilab-common', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
     depends_on('py-six', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-astropy/package.py b/var/spack/repos/builtin/packages/py-astropy/package.py
index 193add18ef..8688e0ee7e 100644
--- a/var/spack/repos/builtin/packages/py-astropy/package.py
+++ b/var/spack/repos/builtin/packages/py-astropy/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyAstropy(Package):
+class PyAstropy(PythonPackage):
     """The Astropy Project is a community effort to develop a single core
     package for Astronomy in Python and foster interoperability between
     Python astronomy packages."""
@@ -37,7 +37,6 @@ class PyAstropy(Package):
     version('1.1.post1', 'b52919f657a37d45cc45f5cb0f58c44d')
 
     # Required dependencies
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
 
     # Optional dependencies
@@ -55,6 +54,5 @@ class PyAstropy(Package):
     depends_on('cfitsio')
     depends_on('expat')
 
-    def install(self, spec, prefix):
-        setup_py('build', '--use-system-cfitsio', '--use-system-expat')
-        setup_py('install', '--prefix={0}'.format(prefix))
+    def build_args(self, spec, prefix):
+        return ['--use-system-cfitsio', '--use-system-expat']
diff --git a/var/spack/repos/builtin/packages/py-autopep8/package.py b/var/spack/repos/builtin/packages/py-autopep8/package.py
index 629a3b515e..c892e2979c 100644
--- a/var/spack/repos/builtin/packages/py-autopep8/package.py
+++ b/var/spack/repos/builtin/packages/py-autopep8/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyAutopep8(Package):
+class PyAutopep8(PythonPackage):
     """autopep8 automatically formats Python code to conform to the
     PEP 8 style guide."""
 
@@ -48,6 +48,3 @@ def url_for_version(self, version):
             return url.format('v', version)
         else:
             return url.format('ver', version)
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-babel/package.py b/var/spack/repos/builtin/packages/py-babel/package.py
index 12d7ff84b6..844ceab34e 100644
--- a/var/spack/repos/builtin/packages/py-babel/package.py
+++ b/var/spack/repos/builtin/packages/py-babel/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyBabel(Package):
+class PyBabel(PythonPackage):
     """Babel is an integrated collection of utilities that assist in
     internationalizing and localizing Python applications, with an
     emphasis on web-based applications."""
@@ -36,10 +36,5 @@ class PyBabel(Package):
     version('2.3.4', 'afa20bc55b0e991833030129ad498f35',
             url="https://pypi.python.org/packages/6e/96/ba2a2462ed25ca0e651fb7b66e7080f5315f91425a07ea5b34d7c870c114/Babel-2.3.4.tar.gz")
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-pytz',       type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-backports-abc/package.py b/var/spack/repos/builtin/packages/py-backports-abc/package.py
index d39cec83c8..7d062bff6a 100644
--- a/var/spack/repos/builtin/packages/py-backports-abc/package.py
+++ b/var/spack/repos/builtin/packages/py-backports-abc/package.py
@@ -25,17 +25,12 @@
 from spack import *
 
 
-class PyBackportsAbc(Package):
+class PyBackportsAbc(PythonPackage):
     """Backports_ABC: A backport of recent additions to the 'collections.abc'
     module."""
     homepage = "https://github.com/cython/backports_abc"
-    # base https://pypi.python.org/pypi/backports_abc/
     url      = "https://github.com/cython/backports_abc/archive/0.4.tar.gz"
 
     version('0.4', 'e4246ae689221c9cbe84369fdb59e8c74d02b298')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-backports-shutil-get-terminal-size/package.py b/var/spack/repos/builtin/packages/py-backports-shutil-get-terminal-size/package.py
index 5158fa3ad4..5950faa765 100644
--- a/var/spack/repos/builtin/packages/py-backports-shutil-get-terminal-size/package.py
+++ b/var/spack/repos/builtin/packages/py-backports-shutil-get-terminal-size/package.py
@@ -25,8 +25,8 @@
 from spack import *
 
 
-class PyBackportsShutilGetTerminalSize(Package):
-    """A backport of the get_terminal_size function 
+class PyBackportsShutilGetTerminalSize(PythonPackage):
+    """A backport of the get_terminal_size function
     from Python 3.3's shutil."""
 
     homepage = "https://pypi.python.org/pypi/backports.shutil_get_terminal_size"
@@ -34,10 +34,5 @@ class PyBackportsShutilGetTerminalSize(Package):
 
     version('1.0.0', '03267762480bd86b50580dc19dff3c66')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@:3.2.999')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-backports-ssl-match-hostname/package.py b/var/spack/repos/builtin/packages/py-backports-ssl-match-hostname/package.py
index 13b3a1abd4..bf4679556b 100644
--- a/var/spack/repos/builtin/packages/py-backports-ssl-match-hostname/package.py
+++ b/var/spack/repos/builtin/packages/py-backports-ssl-match-hostname/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyBackportsSslMatchHostname(Package):
+class PyBackportsSslMatchHostname(PythonPackage):
     """The ssl.match_hostname() function from Python 3.5"""
 
     homepage = "https://pypi.python.org/pypi/backports.ssl_match_hostname"
@@ -33,9 +33,4 @@ class PyBackportsSslMatchHostname(Package):
 
     version('3.5.0.1', 'c03fc5e2c7b3da46b81acf5cbacfe1e6')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-basemap/package.py b/var/spack/repos/builtin/packages/py-basemap/package.py
index d69051037b..4a35134e40 100644
--- a/var/spack/repos/builtin/packages/py-basemap/package.py
+++ b/var/spack/repos/builtin/packages/py-basemap/package.py
@@ -26,7 +26,7 @@
 import os
 
 
-class PyBasemap(Package):
+class PyBasemap(PythonPackage):
     """The matplotlib basemap toolkit is a library for plotting
     2D data on maps in Python."""
 
@@ -35,17 +35,18 @@ class PyBasemap(Package):
 
     version('1.0.7', '48c0557ced9e2c6e440b28b3caff2de8')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('pil', type=('build', 'run'))
-    depends_on("geos")
+    depends_on('geos')
 
-    def install(self, spec, prefix):
-        env['GEOS_DIR'] = spec['geos'].prefix
-        setup_py('install', '--prefix=%s' % prefix)
+    def setup_environment(self, spack_env, run_env):
+        spack_env.set('GEOS_DIR', self.spec['geos'].prefix)
 
+    @PythonPackage.sanity_check('install')
+    def post_install_patch(self):
+        spec = self.spec
         # We are not sure if this fix is needed before Python 3.5.2.
         # If it is needed, this test should be changed.
         # See: https://github.com/LLNL/spack/pull/1964
diff --git a/var/spack/repos/builtin/packages/py-beautifulsoup4/package.py b/var/spack/repos/builtin/packages/py-beautifulsoup4/package.py
index 5f8b636985..3a90d02127 100644
--- a/var/spack/repos/builtin/packages/py-beautifulsoup4/package.py
+++ b/var/spack/repos/builtin/packages/py-beautifulsoup4/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyBeautifulsoup4(Package):
+class PyBeautifulsoup4(PythonPackage):
     """Beautiful Soup is a Python library for pulling data out of HTML and
     XML files. It works with your favorite parser to provide idiomatic ways
     of navigating, searching, and modifying the parse tree."""
@@ -38,9 +38,4 @@ class PyBeautifulsoup4(Package):
             'download/4.5/beautifulsoup4-4.5.1.tar.gz')
     version('4.4.1', '8fbd9a7cac0704645fa20d1419036815')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-biopython/package.py b/var/spack/repos/builtin/packages/py-biopython/package.py
index 83ede166d6..3411e244f9 100644
--- a/var/spack/repos/builtin/packages/py-biopython/package.py
+++ b/var/spack/repos/builtin/packages/py-biopython/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyBiopython(Package):
+class PyBiopython(PythonPackage):
     """A distributed collaborative effort to develop Python libraries and
        applications which address the needs of current and future work in
        bioinformatics.
@@ -36,9 +36,5 @@ class PyBiopython(Package):
 
     version('1.65', '143e7861ade85c0a8b5e2bbdd1da1f67')
 
-    extends('python')
     depends_on('py-mx', type=('build', 'run'))
     depends_on('py-numpy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-bleach/package.py b/var/spack/repos/builtin/packages/py-bleach/package.py
new file mode 100644
index 0000000000..4a6d7ed9d2
--- /dev/null
+++ b/var/spack/repos/builtin/packages/py-bleach/package.py
@@ -0,0 +1,39 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class PyBleach(PythonPackage):
+    """An easy whitelist-based HTML-sanitizing tool."""
+
+    homepage = "http://github.com/mozilla/bleach"
+    url      = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz"
+
+    version('1.5.0', 'b663300efdf421b3b727b19d7be9c7e7')
+
+    depends_on('python@2.6:2.7,3.2:3.5')
+    depends_on('py-setuptools', type='build')
+    depends_on('py-six', type=('build', 'run'))
+    depends_on('py-html5lib@0.999,0.999999:0.9999999', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-blessings/package.py b/var/spack/repos/builtin/packages/py-blessings/package.py
index 700c0ff4b3..b38f34b412 100644
--- a/var/spack/repos/builtin/packages/py-blessings/package.py
+++ b/var/spack/repos/builtin/packages/py-blessings/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyBlessings(Package):
+class PyBlessings(PythonPackage):
     """A nicer, kinder way to write to the terminal """
     homepage = "https://github.com/erikrose/blessings"
     url      = "https://pypi.python.org/packages/source/b/blessings/blessings-1.6.tar.gz"
@@ -33,8 +33,3 @@ class PyBlessings(Package):
     version('1.6', '4f552a8ebcd4982693c92571beb99394')
 
     depends_on('py-setuptools', type='build')
-
-    extends("python")
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-bottleneck/package.py b/var/spack/repos/builtin/packages/py-bottleneck/package.py
index d2d258105d..a1215ce39e 100644
--- a/var/spack/repos/builtin/packages/py-bottleneck/package.py
+++ b/var/spack/repos/builtin/packages/py-bottleneck/package.py
@@ -25,15 +25,11 @@
 from spack import *
 
 
-class PyBottleneck(Package):
+class PyBottleneck(PythonPackage):
     """A collection of fast NumPy array functions written in Cython."""
     homepage = "https://pypi.python.org/pypi/Bottleneck/1.0.0"
     url      = "https://pypi.python.org/packages/source/B/Bottleneck/Bottleneck-1.0.0.tar.gz"
 
     version('1.0.0', '380fa6f275bd24f27e7cf0e0d752f5d2')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-cclib/package.py b/var/spack/repos/builtin/packages/py-cclib/package.py
index f06a8cbc37..b59376d7b8 100644
--- a/var/spack/repos/builtin/packages/py-cclib/package.py
+++ b/var/spack/repos/builtin/packages/py-cclib/package.py
@@ -25,18 +25,13 @@
 from spack import *
 
 
-class PyCclib(Package):
+class PyCclib(PythonPackage):
     """Open source library for parsing and interpreting the results of
     computational chemistry packages"""
 
     homepage = "https://cclib.github.io/"
-    url      = "https://github.com/cclib/cclib/releases/download/v1.5/cclib-1.5.tar.gz"
 
-    version('1.5', 'c06940101c4796bce82036b13fecb73c')
-
-    extends('python')
+    version('1.5.post1', '1a50be48e4597b1a6dabe943da82a43c',
+            url="https://github.com/cclib/cclib/releases/download/v1.5/cclib-1.5.post1.tar.gz")
 
     depends_on('py-numpy@1.5:', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-cdo/package.py b/var/spack/repos/builtin/packages/py-cdo/package.py
index c74a3f7c33..5eb8f414a8 100644
--- a/var/spack/repos/builtin/packages/py-cdo/package.py
+++ b/var/spack/repos/builtin/packages/py-cdo/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyCdo(Package):
+class PyCdo(PythonPackage):
     """The cdo package provides an interface to the Climate Data
     Operators from Python."""
 
@@ -35,13 +35,8 @@ class PyCdo(Package):
     version('1.3.2', '4b3686ec1b9b891f166c1c466c6db745',
             url="https://pypi.python.org/packages/d6/13/908e7c1451e1f5fb68405f341cdcb3196a16952ebfe1f172cb788f864aa9/cdo-1.3.2.tar.gz")
 
-    extends('python')
-
     depends_on('cdo')
 
     depends_on('py-setuptools', type='build')
     depends_on('py-scipy', type=('build', 'run'))
-    depends_on('py-netcdf', type=('build', 'run'))   
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-netcdf', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-certifi/package.py b/var/spack/repos/builtin/packages/py-certifi/package.py
index 996a90df8e..959c0221ed 100644
--- a/var/spack/repos/builtin/packages/py-certifi/package.py
+++ b/var/spack/repos/builtin/packages/py-certifi/package.py
@@ -25,18 +25,13 @@
 from spack import *
 
 
-class PyCertifi(Package):
+class PyCertifi(PythonPackage):
     """Certifi: A carefully curated collection of Root Certificates for validating
     the trustworthiness of SSL certificates while verifying the identity of TLS
     hosts."""
     homepage = "https://github.com/certifi/python-certifi"
-    # base https://pypi.python.org/pypi/certifi/
     url      = "https://github.com/certifi/python-certifi/archive/2016.02.28.tar.gz"
 
     version('2016.02.28', '5ccfc23bd5e931863f0b01ef3e9d2dbd3bef0e1b')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-cffi/package.py b/var/spack/repos/builtin/packages/py-cffi/package.py
index f2f7646687..c0fbae639b 100644
--- a/var/spack/repos/builtin/packages/py-cffi/package.py
+++ b/var/spack/repos/builtin/packages/py-cffi/package.py
@@ -22,12 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import os
-
 from spack import *
 
 
-class PyCffi(Package):
+class PyCffi(PythonPackage):
     """Foreign Function Interface for Python calling C code"""
     homepage = "http://cffi.readthedocs.org/en/latest/"
     # base https://pypi.python.org/pypi/cffi
@@ -35,18 +33,15 @@ class PyCffi(Package):
 
     version('1.1.2', 'ca6e6c45b45caa87aee9adc7c796eaea')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
     depends_on('py-pycparser', type=('build', 'run'))
     depends_on('libffi')
 
-    def install(self, spec, prefix):
+    def setup_environment(self, spack_env, run_env):
         # This sets the compiler (and flags) that distutils will use
         # to create the final shared library.  It will use the
         # compiler specified by the environment variable 'CC' for all
         # other compilation.  We are setting the 'LDSHARED" to the
         # spack compiler wrapper plus a few extra flags necessary for
         # building the shared library.
-        os.environ['LDSHARED'] = "{0} -shared -pthread".format(spack_cc)
-
-        setup_py('install', '--prefix=%s' % prefix)
+        spack_env.set('LDSHARED', "{0} -shared -pthread".format(spack_cc))
diff --git a/var/spack/repos/builtin/packages/py-configparser/package.py b/var/spack/repos/builtin/packages/py-configparser/package.py
index 6885d6bd93..c9ba7ac15c 100644
--- a/var/spack/repos/builtin/packages/py-configparser/package.py
+++ b/var/spack/repos/builtin/packages/py-configparser/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyConfigparser(Package):
+class PyConfigparser(PythonPackage):
     """This library brings the updated configparser from Python 3.5 to
     Python 2.6-3.5."""
 
@@ -35,12 +35,10 @@ class PyConfigparser(Package):
     version('3.5.0', 'cfdd915a5b7a6c09917a64a573140538',
             url="https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz")
 
-    extends('python')
     depends_on('python@2.6:2.7,3.4:')
 
-    depends_on('py-ordereddict', when='^python@2.6:2.6.999', type=('build', 'run'))
+    # This dependency breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-ordereddict', when='^python@2.6:2.6.999', type=('build', 'run'))  # noqa
 
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-coverage/package.py b/var/spack/repos/builtin/packages/py-coverage/package.py
index 8cd8440b7e..dc8fc08e24 100644
--- a/var/spack/repos/builtin/packages/py-coverage/package.py
+++ b/var/spack/repos/builtin/packages/py-coverage/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyCoverage(Package):
+class PyCoverage(PythonPackage):
     """ Testing coverage checker for python """
 
     homepage = "http://nedbatchelder.com/code/coverage/"
@@ -34,8 +34,3 @@ class PyCoverage(Package):
     version('4.0a6', '1bb4058062646148965bef0796b61efc')
 
     depends_on('py-setuptools', type='build')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-csvkit/package.py b/var/spack/repos/builtin/packages/py-csvkit/package.py
index ceb220eef7..5bcda9f449 100644
--- a/var/spack/repos/builtin/packages/py-csvkit/package.py
+++ b/var/spack/repos/builtin/packages/py-csvkit/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyCsvkit(Package):
+class PyCsvkit(PythonPackage):
     """A library of utilities for working with CSV, the king of tabular file
     formats"""
 
@@ -34,14 +34,9 @@ class PyCsvkit(Package):
 
     version('0.9.1', '48d78920019d18846933ee969502fff6')
 
-    extends('python')
-
     depends_on('py-dateutil', type=('build', 'run'))
     depends_on('py-dbf', type=('build', 'run'))
     depends_on('py-xlrd', type=('build', 'run'))
     depends_on('py-sqlalchemy', type=('build', 'run'))
     depends_on('py-six', type=('build', 'run'))
     depends_on('py-openpyxl', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-cycler/package.py b/var/spack/repos/builtin/packages/py-cycler/package.py
index dff307cddd..f2b2a15018 100644
--- a/var/spack/repos/builtin/packages/py-cycler/package.py
+++ b/var/spack/repos/builtin/packages/py-cycler/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyCycler(Package):
+class PyCycler(PythonPackage):
     """Composable style cycles."""
 
     homepage = "http://matplotlib.org/cycler/"
@@ -33,10 +33,5 @@ class PyCycler(Package):
 
     version('0.10.0', '83dd0df7810e838b59e4dd9fa6e2d198')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-six',        type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-cython/package.py b/var/spack/repos/builtin/packages/py-cython/package.py
index 55a4ef9ced..c84728cf3e 100644
--- a/var/spack/repos/builtin/packages/py-cython/package.py
+++ b/var/spack/repos/builtin/packages/py-cython/package.py
@@ -25,21 +25,16 @@
 from spack import *
 
 
-class PyCython(Package):
+class PyCython(PythonPackage):
     """The Cython compiler for writing C extensions for the Python language."""
     homepage = "https://pypi.python.org/pypi/cython"
     url      = "https://pypi.io/packages/source/c/cython/Cython-0.25.2.tar.gz"
 
     version('0.25.2', '642c81285e1bb833b14ab3f439964086')
-    
+
     version('0.23.5', '66b62989a67c55af016c916da36e7514')
     version('0.23.4', '157df1f69bcec6b56fd97e0f2e057f6e')
 
     # These versions contain illegal Python3 code...
     version('0.22', '1ae25add4ef7b63ee9b4af697300d6b6')
     version('0.21.2', 'd21adb870c75680dc857cd05d41046a4')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-dask/package.py b/var/spack/repos/builtin/packages/py-dask/package.py
index c72046b627..4113c2ac0b 100644
--- a/var/spack/repos/builtin/packages/py-dask/package.py
+++ b/var/spack/repos/builtin/packages/py-dask/package.py
@@ -25,16 +25,11 @@
 from spack import *
 
 
-class PyDask(Package):
+class PyDask(PythonPackage):
     """Minimal task scheduling abstraction"""
     homepage = "https://github.com/dask/dask/"
     url      = "https://pypi.python.org/packages/source/d/dask/dask-0.8.1.tar.gz"
 
     version('0.8.1', '5dd8e3a3823b3bc62c9a6d192e2cb5b4')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-dateutil/package.py b/var/spack/repos/builtin/packages/py-dateutil/package.py
index c9e77b3b08..3ab5ad029c 100644
--- a/var/spack/repos/builtin/packages/py-dateutil/package.py
+++ b/var/spack/repos/builtin/packages/py-dateutil/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyDateutil(Package):
+class PyDateutil(PythonPackage):
     """Extensions to the standard Python datetime module."""
     homepage = "https://pypi.python.org/pypi/dateutil"
     url      = "https://pypi.python.org/packages/source/p/python-dateutil/python-dateutil-2.4.0.tar.gz"
@@ -34,9 +34,5 @@ class PyDateutil(Package):
     version('2.4.2', '4ef68e1c485b09e9f034e10473e5add2')
     version('2.5.2', 'eafe168e8f404bf384514f5116eedbb6')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
     depends_on('py-six', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-dbf/package.py b/var/spack/repos/builtin/packages/py-dbf/package.py
index eff893cc82..56403405e8 100644
--- a/var/spack/repos/builtin/packages/py-dbf/package.py
+++ b/var/spack/repos/builtin/packages/py-dbf/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyDbf(Package):
+class PyDbf(PythonPackage):
     """Pure python package for reading/writing dBase, FoxPro, and Visual FoxPro
     .dbf files (including memos)"""
 
@@ -33,8 +33,3 @@ class PyDbf(Package):
     url      = "https://pypi.python.org/packages/source/d/dbf/dbf-0.96.005.tar.gz"
 
     version('0.96.005', 'bce1a1ed8b454a30606e7e18dd2f8277')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-decorator/package.py b/var/spack/repos/builtin/packages/py-decorator/package.py
index 30f764edc3..e5734866ec 100644
--- a/var/spack/repos/builtin/packages/py-decorator/package.py
+++ b/var/spack/repos/builtin/packages/py-decorator/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyDecorator(Package):
+class PyDecorator(PythonPackage):
     """The aim of the decorator module it to simplify the usage of decorators
        for the average programmer, and to popularize decorators by showing
        various non-trivial examples."""
@@ -34,9 +34,4 @@ class PyDecorator(Package):
 
     version('4.0.9', 'f12c5651ccd707e12a0abaa4f76cd69a')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-docutils/package.py b/var/spack/repos/builtin/packages/py-docutils/package.py
index 114131df90..00741284df 100644
--- a/var/spack/repos/builtin/packages/py-docutils/package.py
+++ b/var/spack/repos/builtin/packages/py-docutils/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyDocutils(Package):
+class PyDocutils(PythonPackage):
     """Docutils is an open-source text processing system for processing
     plaintext documentation into useful formats, such as HTML, LaTeX,
     man-pages, open-document or XML. It includes reStructuredText, the
@@ -35,9 +35,6 @@ class PyDocutils(Package):
     homepage = "http://docutils.sourceforge.net/"
     url      = "https://pypi.python.org/packages/source/d/docutils/docutils-0.12.tar.gz"
 
-    version('0.12', '4622263b62c5c771c03502afa3157768')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    version('0.13.1', 'ea4a893c633c788be9b8078b6b305d53',
+            url="https://pypi.python.org/packages/05/25/7b5484aca5d46915493f1fd4ecb63c38c333bd32aa9ad6e19da8d08895ae/docutils-0.13.1.tar.gz")
+    version('0.12',   '4622263b62c5c771c03502afa3157768')
diff --git a/var/spack/repos/builtin/packages/py-emcee/package.py b/var/spack/repos/builtin/packages/py-emcee/package.py
index d3627b32c5..6419a9c40e 100644
--- a/var/spack/repos/builtin/packages/py-emcee/package.py
+++ b/var/spack/repos/builtin/packages/py-emcee/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyEmcee(Package):
+class PyEmcee(PythonPackage):
     """emcee is an MIT licensed pure-Python implementation of Goodman & Weare's
     Affine Invariant Markov chain Monte Carlo (MCMC) Ensemble sampler."""
 
@@ -34,8 +34,4 @@ class PyEmcee(Package):
 
     version('2.1.0', 'c6b6fad05c824d40671d4a4fc58dfff7')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-enum34/package.py b/var/spack/repos/builtin/packages/py-enum34/package.py
index 584572b47d..cc111ce092 100644
--- a/var/spack/repos/builtin/packages/py-enum34/package.py
+++ b/var/spack/repos/builtin/packages/py-enum34/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyEnum34(Package):
+class PyEnum34(PythonPackage):
     """Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4."""
 
     homepage = "https://pypi.python.org/pypi/enum34"
@@ -34,12 +34,10 @@ class PyEnum34(Package):
     version('1.1.6', '5f13a0841a61f7fc295c514490d120d0',
             url="https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz")
 
-    extends('python')
     depends_on('python@2.4:2.8,3.3:')
 
-    depends_on('py-ordereddict', when='^python@:2.6.999', type=('build', 'run'))
+    # This dependency breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-ordereddict', when='^python@:2.6.999', type=('build', 'run'))  # noqa
 
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-epydoc/package.py b/var/spack/repos/builtin/packages/py-epydoc/package.py
index ed490cb396..e13d431f91 100644
--- a/var/spack/repos/builtin/packages/py-epydoc/package.py
+++ b/var/spack/repos/builtin/packages/py-epydoc/package.py
@@ -25,15 +25,10 @@
 from spack import *
 
 
-class PyEpydoc(Package):
+class PyEpydoc(PythonPackage):
     """Epydoc is a tool for generating API documentation documentation for
        Python modules, based on their docstrings."""
     homepage = "https://pypi.python.org/pypi/epydoc"
     url      = "https://pypi.python.org/packages/source/e/epydoc/epydoc-3.0.1.tar.gz"
 
     version('3.0.1', '36407974bd5da2af00bf90ca27feeb44')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-flake8/package.py b/var/spack/repos/builtin/packages/py-flake8/package.py
index 4515f68e0a..17d7cea343 100644
--- a/var/spack/repos/builtin/packages/py-flake8/package.py
+++ b/var/spack/repos/builtin/packages/py-flake8/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyFlake8(Package):
+class PyFlake8(PythonPackage):
     """Flake8 is a wrapper around PyFlakes, pep8 and Ned Batchelder's
     McCabe script."""
 
@@ -43,7 +43,7 @@ class PyFlake8(Package):
     depends_on('py-setuptools', type=('build', 'run'))
 
     # pyflakes >= 0.8.1, != 1.2.0, != 1.2.1, != 1.2.2, < 1.3.0
-    depends_on('py-pyflakes@0.8.1:1.1.0,1.2.3:1.2.3', when='@3.0.4', type=('build', 'run'))  # noqa
+    depends_on('py-pyflakes@0.8.1:1.1.0,1.2.3:1.2.3', when='@3.0.4', type=('build', 'run'))
     # pyflakes >= 0.8.1, < 1.1
     depends_on('py-pyflakes@0.8.1:1.0.0', when='@2.5.4', type=('build', 'run'))
 
@@ -57,11 +57,12 @@ class PyFlake8(Package):
     # mccabe >= 0.2.1, < 0.5
     depends_on('py-mccabe@0.2.1:0.4.0', when='@2.5.4', type=('build', 'run'))
 
-    depends_on('py-configparser', when='^python@:3.3.999', type=('build', 'run'))
-    depends_on('py-enum34', when='^python@:3.1.999', type=('build', 'run'))
+    # These dependencies breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-configparser', when='^python@:3.3.999', type=('build', 'run'))  # noqa
+    # depends_on('py-enum34', when='^python@:3.1.999', type=('build', 'run'))
+    depends_on('py-configparser', type=('build', 'run'))
+    depends_on('py-enum34', type=('build', 'run'))
 
     # TODO: Add test dependencies
     # depends_on('py-nose', type='test')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-funcsigs/package.py b/var/spack/repos/builtin/packages/py-funcsigs/package.py
index b82a37cae9..ea8b71f25f 100644
--- a/var/spack/repos/builtin/packages/py-funcsigs/package.py
+++ b/var/spack/repos/builtin/packages/py-funcsigs/package.py
@@ -25,16 +25,11 @@
 from spack import *
 
 
-class PyFuncsigs(Package):
+class PyFuncsigs(PythonPackage):
     """Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2."""
     homepage = "https://pypi.python.org/pypi/funcsigs"
     url      = "https://pypi.python.org/packages/source/f/funcsigs/funcsigs-0.4.tar.gz"
 
     version('0.4', 'fb1d031f284233e09701f6db1281c2a5')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-functools32/package.py b/var/spack/repos/builtin/packages/py-functools32/package.py
index dc4e49d278..f2fb0df555 100644
--- a/var/spack/repos/builtin/packages/py-functools32/package.py
+++ b/var/spack/repos/builtin/packages/py-functools32/package.py
@@ -25,17 +25,11 @@
 from spack import *
 
 
-class PyFunctools32(Package):
+class PyFunctools32(PythonPackage):
     """Backport of the functools module from Python 3.2.3 for use on 2.7 and
     PyPy."""
 
     homepage = "https://github.com/MiCHiLU/python-functools32"
-    # base https://pypi.python.org/pypi/functools32
     url      = "https://pypi.python.org/packages/source/f/functools32/functools32-3.2.3-2.tar.gz"
 
     version('3.2.3-2', '09f24ffd9af9f6cd0f63cb9f4e23d4b2')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-futures/package.py b/var/spack/repos/builtin/packages/py-futures/package.py
index 20bf4c7dae..c6c1d8134f 100644
--- a/var/spack/repos/builtin/packages/py-futures/package.py
+++ b/var/spack/repos/builtin/packages/py-futures/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyFutures(Package):
+class PyFutures(PythonPackage):
     """Backport of the concurrent.futures package from Python 3.2"""
 
     homepage = "https://pypi.python.org/pypi/futures"
@@ -33,9 +33,4 @@ class PyFutures(Package):
 
     version('3.0.5', 'ced2c365e518242512d7a398b515ff95')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-genders/package.py b/var/spack/repos/builtin/packages/py-genders/package.py
index f919a7e6c2..2123f4eb3f 100644
--- a/var/spack/repos/builtin/packages/py-genders/package.py
+++ b/var/spack/repos/builtin/packages/py-genders/package.py
@@ -36,6 +36,9 @@ class PyGenders(Package):
             url='https://github.com/chaos/genders/releases/download/genders-1-22-1/genders-1.22.tar.gz')
     extends('python')
 
+    # FIXME: Missing a dependency on genders
+    # #include <genders.h>
+
     def install(self, spec, prefix):
         configure("--prefix=%s" % prefix)
         make(parallel=False)
diff --git a/var/spack/repos/builtin/packages/py-genshi/package.py b/var/spack/repos/builtin/packages/py-genshi/package.py
index e7eb7bebdd..462dbfe802 100644
--- a/var/spack/repos/builtin/packages/py-genshi/package.py
+++ b/var/spack/repos/builtin/packages/py-genshi/package.py
@@ -22,11 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import version, extends, depends_on
-from spack import Package
+from spack import *
 
 
-class PyGenshi(Package):
+class PyGenshi(PythonPackage):
     """Python toolkit for generation of output for the web"""
     homepage = "https://genshi.edgewall.org/"
     url      = "http://ftp.edgewall.com/pub/genshi/Genshi-0.7.tar.gz"
@@ -35,8 +34,4 @@ class PyGenshi(Package):
     version('0.6.1', '372c368c8931110b0a521fa6091742d7')
     version('0.6', '604e8b23b4697655d36a69c2d8ef7187')
 
-    extends("python")
     depends_on("py-setuptools", type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-gnuplot/package.py b/var/spack/repos/builtin/packages/py-gnuplot/package.py
index f7b88d00fe..a23aa2585f 100644
--- a/var/spack/repos/builtin/packages/py-gnuplot/package.py
+++ b/var/spack/repos/builtin/packages/py-gnuplot/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyGnuplot(Package):
+class PyGnuplot(PythonPackage):
     """Gnuplot.py is a Python package that allows you to create graphs from
        within Python using the gnuplot plotting program."""
     homepage = "http://gnuplot-py.sourceforge.net/"
@@ -33,8 +33,4 @@ class PyGnuplot(Package):
 
     version('1.8', 'abd6f571e7aec68ae7db90a5217cd5b1')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-h5py/package.py b/var/spack/repos/builtin/packages/py-h5py/package.py
index dffd2463d3..666905e5c0 100644
--- a/var/spack/repos/builtin/packages/py-h5py/package.py
+++ b/var/spack/repos/builtin/packages/py-h5py/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyH5py(Package):
+class PyH5py(PythonPackage):
     """The h5py package provides both a high- and low-level interface to the
     HDF5 library from Python."""
 
@@ -38,8 +38,6 @@ class PyH5py(Package):
 
     variant('mpi', default=True, description='Build with MPI support')
 
-    extends('python')
-
     # Build dependencies
     depends_on('py-cython@0.19:', type='build')
     depends_on('pkg-config', type='build')
@@ -55,11 +53,11 @@ class PyH5py(Package):
     # Runtime dependencies
     depends_on('py-six', type=('build', 'run'))
 
-    def install(self, spec, prefix):
-        setup_py('configure', '--hdf5={0}'.format(spec['hdf5'].prefix))
+    phases = ['configure', 'install']
+
+    def configure(self, spec, prefix):
+        self.setup_py('configure', '--hdf5={0}'.format(spec['hdf5'].prefix))
 
         if '+mpi' in spec:
             env['CC'] = spec['mpi'].mpicc
-            setup_py('configure', '--mpi')
-
-        setup_py('install', '--prefix={0}'.format(prefix))
+            self.setup_py('configure', '--mpi')
diff --git a/var/spack/repos/builtin/packages/py-html5lib/package.py b/var/spack/repos/builtin/packages/py-html5lib/package.py
new file mode 100644
index 0000000000..1757b44297
--- /dev/null
+++ b/var/spack/repos/builtin/packages/py-html5lib/package.py
@@ -0,0 +1,37 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class PyHtml5lib(PythonPackage):
+    """HTML parser based on the WHATWG HTML specification."""
+
+    homepage = "https://github.com/html5lib/html5lib-python"
+    url      = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz"
+
+    version('0.9999999', 'ef43cb05e9e799f25d65d1135838a96f')
+
+    depends_on('python@2.6:2.7,3.2:3.4')
+    depends_on('py-six', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-imagesize/package.py b/var/spack/repos/builtin/packages/py-imagesize/package.py
index 941e64610e..a2d08f6502 100644
--- a/var/spack/repos/builtin/packages/py-imagesize/package.py
+++ b/var/spack/repos/builtin/packages/py-imagesize/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyImagesize(Package):
+class PyImagesize(PythonPackage):
     """Parses image file headers and returns image size. Supports PNG, JPEG,
     JPEG2000, and GIF image file formats."""
 
@@ -35,9 +35,4 @@ class PyImagesize(Package):
     version('0.7.1', '976148283286a6ba5f69b0f81aef8052',
             url="https://pypi.python.org/packages/53/72/6c6f1e787d9cab2cc733cf042f125abec07209a58308831c9f292504e826/imagesize-0.7.1.tar.gz")
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-iminuit/package.py b/var/spack/repos/builtin/packages/py-iminuit/package.py
index 3162b41316..0b93a0f2b8 100644
--- a/var/spack/repos/builtin/packages/py-iminuit/package.py
+++ b/var/spack/repos/builtin/packages/py-iminuit/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyIminuit(Package):
+class PyIminuit(PythonPackage):
     """Interactive IPython-Friendly Minimizer based on SEAL Minuit2."""
 
     homepage = "https://pypi.python.org/pypi/iminuit"
@@ -34,13 +34,9 @@ class PyIminuit(Package):
     version('1.2', '4701ec472cae42015e26251703e6e984')
 
     # Required dependencies
-    extends('python')
     depends_on('py-setuptools', type='build')
 
     # Optional dependencies
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('py-cython', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-ipykernel/package.py b/var/spack/repos/builtin/packages/py-ipykernel/package.py
index d68cfa2ae4..0303a8e43c 100644
--- a/var/spack/repos/builtin/packages/py-ipykernel/package.py
+++ b/var/spack/repos/builtin/packages/py-ipykernel/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyIpykernel(Package):
+class PyIpykernel(PythonPackage):
     """IPython Kernel for Jupyter"""
 
     homepage = "https://pypi.python.org/pypi/ipykernel"
@@ -42,15 +42,10 @@ class PyIpykernel(Package):
     version('4.1.1', '51376850c46fb006e1f8d1cd353507c5')
     version('4.1.0', '638a43e4f8a15872f749090c3f0827b6')
 
-    extends('python')
-
     depends_on('python@2.7:2.7.999,3.3:')
     depends_on('py-setuptools', type='build')
-    depends_on('py-traitlets@4.1.0:')
-    depends_on('py-tornado@4.0:')
-    depends_on('py-ipython@4.0:')
-    depends_on('py-jupyter-client')
-    depends_on('py-pexpect')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-traitlets@4.1.0:', type=('build', 'run'))
+    depends_on('py-tornado@4.0:', type=('build', 'run'))
+    depends_on('py-ipython@4.0:', type=('build', 'run'))
+    depends_on('py-jupyter-client', type=('build', 'run'))
+    depends_on('py-pexpect', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-ipython-genutils/package.py b/var/spack/repos/builtin/packages/py-ipython-genutils/package.py
index 75b20e08e9..66e8a02130 100644
--- a/var/spack/repos/builtin/packages/py-ipython-genutils/package.py
+++ b/var/spack/repos/builtin/packages/py-ipython-genutils/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyIpythonGenutils(Package):
+class PyIpythonGenutils(PythonPackage):
     """Vestigial utilities from IPython"""
 
     homepage = "https://pypi.python.org/pypi/ipython_genutils"
@@ -33,10 +33,5 @@ class PyIpythonGenutils(Package):
 
     version('0.1.0', '9a8afbe0978adbcbfcb3b35b2d015a56')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-ipython/package.py b/var/spack/repos/builtin/packages/py-ipython/package.py
index b8ad5bf03a..277e090faa 100644
--- a/var/spack/repos/builtin/packages/py-ipython/package.py
+++ b/var/spack/repos/builtin/packages/py-ipython/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyIpython(Package):
+class PyIpython(PythonPackage):
     """IPython provides a rich toolkit to help you make the most out of using
        Python interactively."""
     homepage = "https://pypi.python.org/pypi/ipython"
@@ -35,13 +35,15 @@ class PyIpython(Package):
     version('3.1.0', 'a749d90c16068687b0ec45a27e72ef8f')
     version('2.3.1', '2b7085525dac11190bfb45bb8ec8dcbf')
 
-    extends('python')
     depends_on('py-pygments', type=('build', 'run'))
     depends_on('py-setuptools', type=('build', 'run'))
-    depends_on('py-backports-shutil-get-terminal-size', when="^python@:3.2.999")
-    depends_on('py-pathlib2', when="^python@:3.3.999")
-    depends_on('py-pickleshare')
-    depends_on('py-simplegeneric')
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    # These dependencies breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-backports-shutil-get-terminal-size', when="^python@:3.2.999")  # noqa
+    # depends_on('py-pathlib2', when="^python@:3.3.999")
+    depends_on('py-backports-shutil-get-terminal-size', type=('build', 'run'))
+    depends_on('py-pathlib2', type=('build', 'run'))
+
+    depends_on('py-pickleshare', type=('build', 'run'))
+    depends_on('py-simplegeneric', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-ipywidgets/package.py b/var/spack/repos/builtin/packages/py-ipywidgets/package.py
index c9b2e9c168..eafee8e084 100644
--- a/var/spack/repos/builtin/packages/py-ipywidgets/package.py
+++ b/var/spack/repos/builtin/packages/py-ipywidgets/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyIpywidgets(Package):
+class PyIpywidgets(PythonPackage):
     """IPython widgets for the Jupyter Notebook"""
 
     homepage = "https://github.com/ipython/ipywidgets"
@@ -33,13 +33,8 @@ class PyIpywidgets(Package):
 
     version('5.2.2', '112f3daa4aa0f42f8dda831cea3649c8')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-ipython@4.0.0:')
-    depends_on('py-ipykernel@4.2.2:')
-    depends_on('py-traitlets@4.2.1:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-ipython@4.0.0:', type=('build', 'run'))
+    depends_on('py-ipykernel@4.2.2:', type=('build', 'run'))
+    depends_on('py-traitlets@4.2.1:', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-jdcal/package.py b/var/spack/repos/builtin/packages/py-jdcal/package.py
index 60ee91c9b6..964db1448f 100644
--- a/var/spack/repos/builtin/packages/py-jdcal/package.py
+++ b/var/spack/repos/builtin/packages/py-jdcal/package.py
@@ -25,15 +25,10 @@
 from spack import *
 
 
-class PyJdcal(Package):
+class PyJdcal(PythonPackage):
     """Julian dates from proleptic Gregorian and Julian calendars"""
 
     homepage = 'http://github.com/phn/jdcal'
     url      = "https://pypi.python.org/packages/source/j/jdcal/jdcal-1.2.tar.gz"
 
     version('1.2', 'ab8d5ba300fd1eb01514f363d19b1eb9')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-jinja2/package.py b/var/spack/repos/builtin/packages/py-jinja2/package.py
index d9191952ee..eafe8c252b 100644
--- a/var/spack/repos/builtin/packages/py-jinja2/package.py
+++ b/var/spack/repos/builtin/packages/py-jinja2/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyJinja2(Package):
+class PyJinja2(PythonPackage):
     """Jinja2 is a template engine written in pure Python. It provides
     a Django inspired non-XML syntax but supports inline expressions
     and an optional sandboxed environment."""
@@ -39,11 +39,6 @@ class PyJinja2(Package):
     version('2.7.1', '282aed153e69f970d6e76f78ed9d027a')
     version('2.7',   'c2fb12cbbb523c57d3d15bfe4dc0e8fe')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-markupsafe', type=('build', 'run'))
     depends_on('py-babel@0.8:', type=('build', 'run'))  # optional, required for i18n
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-jsonschema/package.py b/var/spack/repos/builtin/packages/py-jsonschema/package.py
index d41ee439db..b1a0ac6606 100644
--- a/var/spack/repos/builtin/packages/py-jsonschema/package.py
+++ b/var/spack/repos/builtin/packages/py-jsonschema/package.py
@@ -25,19 +25,18 @@
 from spack import *
 
 
-class PyJsonschema(Package):
+class PyJsonschema(PythonPackage):
     """Jsonschema: An(other) implementation of JSON Schema for Python."""
 
     homepage = "http://github.com/Julian/jsonschema"
-    # base https://pypi.python.org/pypi/jsonschema
     url      = "https://pypi.python.org/packages/source/j/jsonschema/jsonschema-2.5.1.tar.gz"
 
     version('2.5.1', '374e848fdb69a3ce8b7e778b47c30640')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-    depends_on('py-vcversioner')
-    depends_on('py-functools32', when="^python@2.7")
+    depends_on('py-vcversioner', type=('build', 'run'))
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    # This dependency breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-functools32', when="^python@2.7", type=('build', 'run'))
+    depends_on('py-functools32', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-jupyter-client/package.py b/var/spack/repos/builtin/packages/py-jupyter-client/package.py
index c8268c8aa2..b0c7b06362 100644
--- a/var/spack/repos/builtin/packages/py-jupyter-client/package.py
+++ b/var/spack/repos/builtin/packages/py-jupyter-client/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyJupyterClient(Package):
+class PyJupyterClient(PythonPackage):
     """Jupyter protocol client APIs"""
 
     homepage = "https://github.com/jupyter/jupyter_client"
@@ -40,13 +40,8 @@ class PyJupyterClient(Package):
     version('4.1.0', 'cf42048b889c8434fbb5813a9eec1d34')
     version('4.0.0', '00fa63c67cb3adf359d09dc4d803aff5')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-traitlets')
-    depends_on('py-jupyter-core')
-    depends_on('py-zmq@13:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-traitlets', type=('build', 'run'))
+    depends_on('py-jupyter-core', type=('build', 'run'))
+    depends_on('py-zmq@13:', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-jupyter-console/package.py b/var/spack/repos/builtin/packages/py-jupyter-console/package.py
index 1cc432e2e0..a5f3e53298 100644
--- a/var/spack/repos/builtin/packages/py-jupyter-console/package.py
+++ b/var/spack/repos/builtin/packages/py-jupyter-console/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyJupyterConsole(Package):
+class PyJupyterConsole(PythonPackage):
     """Jupyter Terminal Console"""
 
     homepage = "https://github.com/jupyter/jupyter_console"
@@ -37,15 +37,10 @@ class PyJupyterConsole(Package):
     version('4.0.3', '0e928ea261e7f8154698cf69ed4f2459')
     version('4.0.2', 'f2e174938c91136549b908bd39fa5d59')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-jupyter-client')
-    depends_on('py-ipython')
-    depends_on('py-ipykernel')
-    depends_on('py-pygments')
-    depends_on('py-prompt-toolkit@1.0.0:1.999.999')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-jupyter-client', type=('build', 'run'))
+    depends_on('py-ipython', type=('build', 'run'))
+    depends_on('py-ipykernel', type=('build', 'run'))
+    depends_on('py-pygments', type=('build', 'run'))
+    depends_on('py-prompt-toolkit@1.0.0:1.999.999', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-jupyter-core/package.py b/var/spack/repos/builtin/packages/py-jupyter-core/package.py
index ae4f192437..f650a91bb9 100644
--- a/var/spack/repos/builtin/packages/py-jupyter-core/package.py
+++ b/var/spack/repos/builtin/packages/py-jupyter-core/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyJupyterCore(Package):
+class PyJupyterCore(PythonPackage):
     """Core Jupyter functionality"""
 
     homepage = "http://jupyter-core.readthedocs.io/"
@@ -40,13 +40,8 @@ class PyJupyterCore(Package):
     version('4.0.3', 'f2608f6e92f992ec8e37646b52c922a6')
     version('4.0.2', 'ae0d0197c4febf43c050a97ac6277263')
     version('4.0.1', 'f849136b2badaaba2a8a3b397bf04639')
-    version('4.0'  , 'b6b37cb4f40bd0fcd20433cb2cc7a4c1')
-
-    extends('python')
+    version('4.0',   'b6b37cb4f40bd0fcd20433cb2cc7a4c1')
 
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-traitlets')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-traitlets', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-jupyter-notebook/package.py b/var/spack/repos/builtin/packages/py-jupyter-notebook/package.py
index c9eb51d2d4..4c0d12b245 100644
--- a/var/spack/repos/builtin/packages/py-jupyter-notebook/package.py
+++ b/var/spack/repos/builtin/packages/py-jupyter-notebook/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyJupyterNotebook(Package):
+class PyJupyterNotebook(PythonPackage):
     """Jupyter Interactive Notebook"""
 
     homepage = "https://github.com/jupyter/notebook"
@@ -44,23 +44,18 @@ class PyJupyterNotebook(Package):
 
     variant('terminal', default=False, description="Enable terminal functionality")
 
-    extends('python')
-    
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
     depends_on('npm', type='build')
-    depends_on('py-jinja2')
-    depends_on('py-tornado@4:')
-    depends_on('py-ipython-genutils')
-    depends_on('py-traitlets')
-    depends_on('py-jupyter-core')
-    depends_on('py-jupyter-client')
-    depends_on('py-jupyter-console')
-    depends_on('py-nbformat')
-    depends_on('py-nbconvert')
-    depends_on('py-ipykernel')
-    depends_on('py-terminado@0.3.3:', when="+terminal")
-    depends_on('py-ipywidgets', when="+terminal")
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-jinja2', type=('build', 'run'))
+    depends_on('py-tornado@4:', type=('build', 'run'))
+    depends_on('py-ipython-genutils', type=('build', 'run'))
+    depends_on('py-traitlets', type=('build', 'run'))
+    depends_on('py-jupyter-core', type=('build', 'run'))
+    depends_on('py-jupyter-client', type=('build', 'run'))
+    depends_on('py-jupyter-console', type=('build', 'run'))
+    depends_on('py-nbformat', type=('build', 'run'))
+    depends_on('py-nbconvert', type=('build', 'run'))
+    depends_on('py-ipykernel', type=('build', 'run'))
+    depends_on('py-terminado@0.3.3:', when="+terminal", type=('build', 'run'))
+    depends_on('py-ipywidgets', when="+terminal", type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-lockfile/package.py b/var/spack/repos/builtin/packages/py-lockfile/package.py
index 856276ec89..1e57e6a1d7 100644
--- a/var/spack/repos/builtin/packages/py-lockfile/package.py
+++ b/var/spack/repos/builtin/packages/py-lockfile/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyLockfile(Package):
+class PyLockfile(PythonPackage):
     """The lockfile package exports a LockFile class which provides a
        simple API for locking files. Unlike the Windows msvcrt.locking
        function, the fcntl.lockf and flock functions, and the
@@ -41,8 +41,4 @@ class PyLockfile(Package):
 
     version('0.10.2', '1aa6175a6d57f082cd12e7ac6102ab15')
 
-    extends("python")
     depends_on("py-setuptools", type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-logilab-common/package.py b/var/spack/repos/builtin/packages/py-logilab-common/package.py
index e8c52493b5..4c20885760 100644
--- a/var/spack/repos/builtin/packages/py-logilab-common/package.py
+++ b/var/spack/repos/builtin/packages/py-logilab-common/package.py
@@ -22,11 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import depends_on, extends, version
-from spack import Package
+from spack import *
 
 
-class PyLogilabCommon(Package):
+class PyLogilabCommon(PythonPackage):
     """Common modules used by Logilab projects"""
     homepage = "https://www.logilab.org/project/logilab-common"
     url      = "https://pypi.python.org/packages/a7/31/1650d23e44794d46935d82b86e73454cc83b814cbe1365260ccce8a2f4c6/logilab-common-1.2.0.tar.gz"
@@ -36,6 +35,3 @@ class PyLogilabCommon(Package):
     extends('python', ignore=r'bin/pytest')
     depends_on("py-setuptools", type='build')
     depends_on("py-six", type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-macs2/package.py b/var/spack/repos/builtin/packages/py-macs2/package.py
index 09cb76c3d3..42318faa2a 100644
--- a/var/spack/repos/builtin/packages/py-macs2/package.py
+++ b/var/spack/repos/builtin/packages/py-macs2/package.py
@@ -26,7 +26,7 @@
 from spack import *
 
 
-class PyMacs2(Package):
+class PyMacs2(PythonPackage):
     """MACS2 Model-based Analysis of ChIP-Seq"""
 
     homepage = "https://github.com/taoliu/MACS"
@@ -34,13 +34,9 @@ class PyMacs2(Package):
 
     version('2.1.1.20160309', '2008ba838f83f34f8e0fddefe2a3a0159f4a740707c68058f815b31ddad53d26')
 
-    extends('python')
     depends_on('python@2.7:2.8')
 
     # Most Python packages only require py-setuptools as a build dependency.
     # However, py-macs2 requires py-setuptools during runtime as well.
     depends_on('py-setuptools', type=('build', 'run'))
     depends_on('py-numpy@1.6:', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-mako/package.py b/var/spack/repos/builtin/packages/py-mako/package.py
index 799bc7b5d9..0707d0b12f 100644
--- a/var/spack/repos/builtin/packages/py-mako/package.py
+++ b/var/spack/repos/builtin/packages/py-mako/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMako(Package):
+class PyMako(PythonPackage):
     """A super-fast templating language that borrows the best
        ideas from the existing templating languages."""
 
@@ -35,12 +35,7 @@ class PyMako(Package):
     version('1.0.4', 'c5fc31a323dd4990683d2f2da02d4e20')
     version('1.0.1', '9f0aafd177b039ef67b90ea350497a54')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     # depends_on('py-mock',   type='test')  # TODO: Add test deptype
     # depends_on('py-pytest', type='test')  # TODO: Add test deptype
     depends_on('py-markupsafe@0.9.2:', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-markdown/package.py b/var/spack/repos/builtin/packages/py-markdown/package.py
index cded451f3b..23c8167021 100644
--- a/var/spack/repos/builtin/packages/py-markdown/package.py
+++ b/var/spack/repos/builtin/packages/py-markdown/package.py
@@ -26,7 +26,7 @@
 from spack import *
 
 
-class PyMarkdown(Package):
+class PyMarkdown(PythonPackage):
     """This is a Python implementation of John Gruber's Markdown. It is
     almost completely compliant with the reference implementation, though
     there are a few very minor differences. See John's Syntax
@@ -48,14 +48,9 @@ class PyMarkdown(Package):
     version('2.5.1', 'be6f6ba65a8fb843d2aaf1fcdd68c755')
     version('2.5', '8393ceab9c6e33357fb8a7be063a4849')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.8,3.2:3.4')
 
     def url_for_version(self, version):
         base_url = "https://github.com/waylan/Python-Markdown/archive"
         return "{0}/{1}-final.tar.gz".format(base_url, version)
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-markupsafe/package.py b/var/spack/repos/builtin/packages/py-markupsafe/package.py
index 5ba5bfb997..a31e3972de 100644
--- a/var/spack/repos/builtin/packages/py-markupsafe/package.py
+++ b/var/spack/repos/builtin/packages/py-markupsafe/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMarkupsafe(Package):
+class PyMarkupsafe(PythonPackage):
     """MarkupSafe is a library for Python that implements a unicode
     string that is aware of HTML escaping rules and can be used to
     implement automatic string escaping. It is used by Jinja 2, the
@@ -40,9 +40,4 @@ class PyMarkupsafe(Package):
     version('0.20', '7da066d9cb191a70aa85d0a3d43565d1')
     version('0.19', 'ccb3f746c807c5500850987006854a6d')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-matplotlib/package.py b/var/spack/repos/builtin/packages/py-matplotlib/package.py
index cac4da9e79..d808b0fc4b 100644
--- a/var/spack/repos/builtin/packages/py-matplotlib/package.py
+++ b/var/spack/repos/builtin/packages/py-matplotlib/package.py
@@ -26,7 +26,7 @@
 import os
 
 
-class PyMatplotlib(Package):
+class PyMatplotlib(PythonPackage):
     """matplotlib is a python 2D plotting library which produces publication
     quality figures in a variety of hardcopy formats and interactive
     environments across platforms."""
@@ -80,7 +80,7 @@ class PyMatplotlib(Package):
 
     # --------- Optional dependencies
     depends_on('pkg-config', type='build')    # why not...
-    depends_on('py-pillow', when='+image', type=('build', 'run'))
+    depends_on('pil', when='+image', type=('build', 'run'))
     depends_on('py-ipython', when='+ipython')
     depends_on('ghostscript', when='+latex', type='run')
     depends_on('texlive', when='+latex', type='run')
@@ -95,9 +95,10 @@ class PyMatplotlib(Package):
     # depends_on('ttconv')
     depends_on('py-six@1.9.0:', type=('build', 'run'))
 
-    def install(self, spec, prefix):
-        setup_py('build')
-        setup_py('install', '--prefix={0}'.format(prefix))
+    @PythonPackage.sanity_check('install')
+    def set_backend(self):
+        spec = self.spec
+        prefix = self.prefix
 
         if '+qt' in spec or '+tk' in spec:
             # Set backend in matplotlib configuration file
diff --git a/var/spack/repos/builtin/packages/py-mccabe/package.py b/var/spack/repos/builtin/packages/py-mccabe/package.py
index ec913acb16..c413193cdc 100644
--- a/var/spack/repos/builtin/packages/py-mccabe/package.py
+++ b/var/spack/repos/builtin/packages/py-mccabe/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMccabe(Package):
+class PyMccabe(PythonPackage):
     """Ned's script to check McCabe complexity."""
 
     homepage = "https://github.com/PyCQA/mccabe"
@@ -41,13 +41,9 @@ class PyMccabe(Package):
     version('0.2',   '36d4808c37e187dbb1fe2373a0ac6645')
     version('0.1',   '3c9e8e72612a9c01d865630cc569150a')
 
-    extends('python')
     depends_on('python@2.7:2.8,3.3:')
 
     depends_on('py-setuptools', type='build')
 
     # TODO: Add test dependencies
     # depends_on('py-pytest', type='test')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-meep/package.py b/var/spack/repos/builtin/packages/py-meep/package.py
index a0d57e7373..0ebba77ac6 100644
--- a/var/spack/repos/builtin/packages/py-meep/package.py
+++ b/var/spack/repos/builtin/packages/py-meep/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMeep(Package):
+class PyMeep(PythonPackage):
     """Python-meep is a wrapper around libmeep. It allows the scripting of
     Meep-simulations with Python"""
 
@@ -36,7 +36,6 @@ class PyMeep(Package):
 
     variant('mpi', default=True, description='Enable MPI support')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-scipy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
@@ -50,9 +49,12 @@ class PyMeep(Package):
     # or else it can't handle newer C++ compilers and flags.
     depends_on('swig@1.3.39:3.0.2')
 
-    def install(self, spec, prefix):
-        setup = 'setup-mpi.py' if '+mpi' in spec else 'setup.py'
+    phases = ['clean', 'build_ext', 'install', 'bdist']
 
+    def setup_file(self, spec, prefix):
+        return 'setup-mpi.py' if '+mpi' in spec else 'setup.py'
+
+    def common_args(self, spec, prefix):
         include_dirs = [
             spec['meep'].prefix.include,
             spec['py-numpy'].include
@@ -69,7 +71,19 @@ def install(self, spec, prefix):
         include_flags = '-I{0}'.format(','.join(include_dirs))
         library_flags = '-L{0}'.format(','.join(library_dirs))
 
-        python(setup, 'clean', '--all')
-        python(setup, 'build_ext', include_flags, library_flags)
-        python(setup, 'install', '--prefix={0}'.format(prefix))
-        python(setup, 'bdist', include_flags, library_flags)
+        # FIXME: For some reason, this stopped working.
+        # The -I and -L are no longer being properly forwarded to setup.py:
+        # meep_common.i:87: Error: Unable to find 'meep/mympi.hpp'
+        # meep_common.i:88: Error: Unable to find 'meep/vec.hpp'
+        # meep_common.i:89: Error: Unable to find 'meep.hpp'
+
+        return [include_flags, library_flags]
+
+    def clean_args(self, spec, prefix):
+        return ['--all']
+
+    def build_ext_args(self, spec, prefix):
+        return self.common_args(spec, prefix)
+
+    def bdist_args(self, spec, prefix):
+        return self.common_args(spec, prefix)
diff --git a/var/spack/repos/builtin/packages/py-mistune/package.py b/var/spack/repos/builtin/packages/py-mistune/package.py
index 2daee1ed9a..cc859d4b78 100644
--- a/var/spack/repos/builtin/packages/py-mistune/package.py
+++ b/var/spack/repos/builtin/packages/py-mistune/package.py
@@ -22,11 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import depends_on, extends, version
-from spack import Package
+from spack import *
 
 
-class PyMistune(Package):
+class PyMistune(PythonPackage):
     """
     Python markdown parser
     """
@@ -39,8 +38,4 @@ class PyMistune(Package):
     version('0.5.1', '1c6cfce28a4aa90cf125217cd6c6fe6c')
     version('0.5', '997736554f1f95eea78c66ae339b5722')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-mock/package.py b/var/spack/repos/builtin/packages/py-mock/package.py
index b33dfe7379..21edbd1dc0 100644
--- a/var/spack/repos/builtin/packages/py-mock/package.py
+++ b/var/spack/repos/builtin/packages/py-mock/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMock(Package):
+class PyMock(PythonPackage):
     """mock is a library for testing in Python. It allows you to replace parts
     of your system under test with mock objects and make assertions about how
     they have been used."""
@@ -35,9 +35,5 @@ class PyMock(Package):
 
     version('1.3.0', '73ee8a4afb3ff4da1b4afa287f39fdeb')
 
-    extends('python')
     depends_on('py-pbr', type=('build', 'run'))
     depends_on('py-setuptools@17.1:', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-monotonic/package.py b/var/spack/repos/builtin/packages/py-monotonic/package.py
index 6584aa248c..b02f954ccc 100644
--- a/var/spack/repos/builtin/packages/py-monotonic/package.py
+++ b/var/spack/repos/builtin/packages/py-monotonic/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMonotonic(Package):
+class PyMonotonic(PythonPackage):
     """An implementation of time.monotonic() for Python 2 & < 3.3"""
 
     homepage = "https://pypi.python.org/pypi/monotonic"
@@ -33,9 +33,4 @@ class PyMonotonic(Package):
 
     version('1.2', 'd14c93aabc3d6af25ef086b032b123cf')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-mpi4py/package.py b/var/spack/repos/builtin/packages/py-mpi4py/package.py
index 11b1584397..7f8dc6b986 100644
--- a/var/spack/repos/builtin/packages/py-mpi4py/package.py
+++ b/var/spack/repos/builtin/packages/py-mpi4py/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMpi4py(Package):
+class PyMpi4py(PythonPackage):
     """This package provides Python bindings for the Message Passing
        Interface (MPI) standard. It is implemented on top of the
        MPI-1/MPI-2 specification and exposes an API which grounds on the
@@ -38,9 +38,5 @@ class PyMpi4py(Package):
     version('2.0.0', '4f7d8126d7367c239fd67615680990e3')
     version('1.3.1', 'dbe9d22bdc8ed965c23a7ceb6f32fc3c')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
     depends_on('mpi')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-mpmath/package.py b/var/spack/repos/builtin/packages/py-mpmath/package.py
index 846852aeb5..d379e0bd03 100644
--- a/var/spack/repos/builtin/packages/py-mpmath/package.py
+++ b/var/spack/repos/builtin/packages/py-mpmath/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyMpmath(Package):
+class PyMpmath(PythonPackage):
     """A Python library for arbitrary-precision floating-point arithmetic."""
     homepage = "http://mpmath.org"
     url      = "https://pypi.python.org/packages/source/m/mpmath/mpmath-all-0.19.tar.gz"
 
     version('0.19', 'd1b7e19dd6830d0d7b5e1bc93d46c02c')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-mx/package.py b/var/spack/repos/builtin/packages/py-mx/package.py
index e72b281665..9af74555b1 100644
--- a/var/spack/repos/builtin/packages/py-mx/package.py
+++ b/var/spack/repos/builtin/packages/py-mx/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyMx(Package):
+class PyMx(PythonPackage):
     """The eGenix.com mx Base Distribution for Python is a collection of
        professional quality software tools which enhance Python's
        usability in many important areas such as fast text searching,
@@ -36,8 +36,3 @@ class PyMx(Package):
     url      = "https://downloads.egenix.com/python/egenix-mx-base-3.2.8.tar.gz"
 
     version('3.2.8', '9d9d3a25f9dc051a15e97f452413423b')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-mysqldb1/package.py b/var/spack/repos/builtin/packages/py-mysqldb1/package.py
index 14534a57ca..8fd794aadb 100644
--- a/var/spack/repos/builtin/packages/py-mysqldb1/package.py
+++ b/var/spack/repos/builtin/packages/py-mysqldb1/package.py
@@ -25,15 +25,14 @@
 from spack import *
 
 
-class PyMysqldb1(Package):
+class PyMysqldb1(PythonPackage):
     """Legacy mysql bindings for python"""
     homepage = "https://github.com/farcepest/MySQLdb1"
     url      = "https://github.com/farcepest/MySQLdb1/archive/MySQLdb-1.2.5.tar.gz"
 
-    version('1.2.5', '332c8f4955b6bc0c79ea15170bf7321b')
+    version('1.2.5', '332c8f4955b6bc0c79ea15170bf7321b',
+            url="https://github.com/farcepest/MySQLdb1/archive/MySQLdb-1.2.5.tar.gz")
 
-    extends('python')
-    depends_on('py-setuptools', type='build')
+    # FIXME: Missing dependency on mysql
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    depends_on('py-setuptools', type='build')
diff --git a/var/spack/repos/builtin/packages/py-nbconvert/package.py b/var/spack/repos/builtin/packages/py-nbconvert/package.py
index 41a1c89686..0e221d355c 100644
--- a/var/spack/repos/builtin/packages/py-nbconvert/package.py
+++ b/var/spack/repos/builtin/packages/py-nbconvert/package.py
@@ -25,30 +25,31 @@
 from spack import *
 
 
-class PyNbconvert(Package):
+class PyNbconvert(PythonPackage):
     """Jupyter Notebook Conversion"""
 
     homepage = "https://github.com/jupyter/nbconvert"
     url      = "https://github.com/jupyter/nbconvert/archive/4.2.0.tar.gz"
 
-    version('4.2.0' , '8bd88771cc00f575d5edcd0b5197f964')
-    version('4.1.0' , '06655576713ba1ff7cece2b92760c187')
-    version('4.0.0' , '9661620b1e10a7b46f314588d2d0932f')
-
-    extends('python')
+    version('4.2.0', '8bd88771cc00f575d5edcd0b5197f964')
+    version('4.1.0', '06655576713ba1ff7cece2b92760c187')
+    version('4.0.0', '9661620b1e10a7b46f314588d2d0932f')
 
     depends_on('py-setuptools', type='build')
     depends_on('py-pycurl', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-mistune')
-    depends_on('py-jinja2')
-    depends_on('py-pygments')
-    depends_on('py-traitlets')
-    depends_on('py-jupyter-core')
-    depends_on('py-nbformat')
-    depends_on('py-entrypoints')
-    depends_on('py-tornado')
-    depends_on('py-jupyter-client')
+    depends_on('py-mistune', type=('build', 'run'))
+    depends_on('py-jinja2', type=('build', 'run'))
+    depends_on('py-pygments', type=('build', 'run'))
+    depends_on('py-traitlets', type=('build', 'run'))
+    depends_on('py-jupyter-core', type=('build', 'run'))
+    depends_on('py-nbformat', type=('build', 'run'))
+    depends_on('py-entrypoints', type=('build', 'run'))
+    depends_on('py-tornado', type=('build', 'run'))
+    depends_on('py-jupyter-client', type=('build', 'run'))
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    # FIXME:
+    # Failed, try again after installing PycURL with `pip install pycurl` to avoid outdated SSL.  # noqa
+    # Failed to download css from https://cdn.jupyter.org/notebook/4.1.0/style/style.min.css: [Errno socket error] [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:661)  # noqa
+    # Downloading CSS: https://cdn.jupyter.org/notebook/4.1.0/style/style.min.css  # noqa
+    # error: Need Notebook CSS to proceed: nbconvert/resources/style.min.css
diff --git a/var/spack/repos/builtin/packages/py-nbformat/package.py b/var/spack/repos/builtin/packages/py-nbformat/package.py
index f45236fc34..4ecf7f8fcf 100644
--- a/var/spack/repos/builtin/packages/py-nbformat/package.py
+++ b/var/spack/repos/builtin/packages/py-nbformat/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyNbformat(Package):
+class PyNbformat(PythonPackage):
     """The Jupyter Notebook format"""
 
     homepage = "https://github.com/jupyter/nbformat"
@@ -35,13 +35,8 @@ class PyNbformat(Package):
     version('4.0.1', 'ab7172e517c9d561c0c01eef5631b4c8')
     version('4.0.0', '7cf61359fa4e9cf3ef5e969e2fcb909e')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-    depends_on('py-ipython-genutils')
-    depends_on('py-traitlets')
-    depends_on('py-jsonschema')
-    depends_on('py-jupyter-core')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-ipython-genutils', type=('build', 'run'))
+    depends_on('py-traitlets', type=('build', 'run'))
+    depends_on('py-jsonschema', type=('build', 'run'))
+    depends_on('py-jupyter-core', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-nestle/package.py b/var/spack/repos/builtin/packages/py-nestle/package.py
index a2ce2f514f..22dc9debe1 100644
--- a/var/spack/repos/builtin/packages/py-nestle/package.py
+++ b/var/spack/repos/builtin/packages/py-nestle/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyNestle(Package):
+class PyNestle(PythonPackage):
     """Nested sampling algorithms for evaluating Bayesian evidence."""
 
     homepage = "http://kbarbary.github.io/nestle/"
@@ -34,11 +34,7 @@ class PyNestle(Package):
     version('0.1.1', '4875c0f9a0a8e263c1d7f5fa6ce604c5')
 
     # Required dependencies
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
 
     # Optional dependencies
     depends_on('py-scipy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-netcdf/package.py b/var/spack/repos/builtin/packages/py-netcdf/package.py
index 967b8e0613..2d35320ca0 100644
--- a/var/spack/repos/builtin/packages/py-netcdf/package.py
+++ b/var/spack/repos/builtin/packages/py-netcdf/package.py
@@ -25,18 +25,14 @@
 from spack import *
 
 
-class PyNetcdf(Package):
+class PyNetcdf(PythonPackage):
     """Python interface to the netCDF Library."""
     homepage = "http://unidata.github.io/netcdf4-python"
     url      = "https://github.com/Unidata/netcdf4-python/tarball/v1.2.3.1rel"
 
     version('1.2.3.1', '4fc4320d4f2a77b894ebf8da1c9895af')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-cython', type=('build', 'run'))
     depends_on('py-setuptools', type=('build', 'run'))
     depends_on('netcdf')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-networkx/package.py b/var/spack/repos/builtin/packages/py-networkx/package.py
index 3086709a79..6eca70c15c 100644
--- a/var/spack/repos/builtin/packages/py-networkx/package.py
+++ b/var/spack/repos/builtin/packages/py-networkx/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyNetworkx(Package):
+class PyNetworkx(PythonPackage):
     """NetworkX is a Python package for the creation, manipulation, and study
     of the structure, dynamics, and functions of complex networks."""
     homepage = "http://networkx.github.io/"
@@ -33,10 +33,5 @@ class PyNetworkx(Package):
 
     version('1.11', '6ef584a879e9163013e9a762e1cf7cd1')
 
-    extends('python')
-
     depends_on('py-decorator', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-nose/package.py b/var/spack/repos/builtin/packages/py-nose/package.py
index f1872c85b4..050a018ffa 100644
--- a/var/spack/repos/builtin/packages/py-nose/package.py
+++ b/var/spack/repos/builtin/packages/py-nose/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyNose(Package):
+class PyNose(PythonPackage):
     """nose extends the test loading and running features of unittest,
     making it easier to write, find and run tests."""
 
@@ -36,8 +36,4 @@ class PyNose(Package):
     version('1.3.6', '0ca546d81ca8309080fc80cb389e7a16')
     version('1.3.7', '4d3ad0ff07b61373d2cefc89c5d0b20b')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-numexpr/package.py b/var/spack/repos/builtin/packages/py-numexpr/package.py
index b8e1ca5e0c..ee89820f5b 100644
--- a/var/spack/repos/builtin/packages/py-numexpr/package.py
+++ b/var/spack/repos/builtin/packages/py-numexpr/package.py
@@ -25,16 +25,16 @@
 from spack import *
 
 
-class PyNumexpr(Package):
+class PyNumexpr(PythonPackage):
     """Fast numerical expression evaluator for NumPy"""
     homepage = "https://pypi.python.org/pypi/numexpr"
     url      = "https://pypi.python.org/packages/source/n/numexpr/numexpr-2.4.6.tar.gz"
 
+    version('2.6.1', '6365245705b446426df9543ad218dd8e',
+            url="https://pypi.python.org/packages/c6/f0/11628fa4d332d8fe9ab0ba8e9bfe0e065fb6b5324859171ee72d84e079c0/numexpr-2.6.1.tar.gz")
+    version('2.5',   '84f66cced45ba3e30dcf77a937763aaa')
     version('2.4.6', '17ac6fafc9ea1ce3eb970b9abccb4fbd')
-    version('2.5', '84f66cced45ba3e30dcf77a937763aaa')
 
-    extends('python')
-    depends_on('py-numpy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    depends_on('python@2.6:')
+    depends_on('py-numpy@1.6:', type=('build', 'run'))
+    depends_on('py-setuptools', type='build')
diff --git a/var/spack/repos/builtin/packages/py-numpy/package.py b/var/spack/repos/builtin/packages/py-numpy/package.py
index 22b4d98b9d..25a5f04b8c 100644
--- a/var/spack/repos/builtin/packages/py-numpy/package.py
+++ b/var/spack/repos/builtin/packages/py-numpy/package.py
@@ -26,7 +26,7 @@
 import platform
 
 
-class PyNumpy(Package):
+class PyNumpy(PythonPackage):
     """NumPy is the fundamental package for scientific computing with Python.
     It contains among other things: a powerful N-dimensional array object,
     sophisticated (broadcasting) functions, tools for integrating C/C++ and
@@ -46,7 +46,6 @@ class PyNumpy(Package):
     variant('blas',   default=True)
     variant('lapack', default=True)
 
-    extends('python')
     depends_on('python@2.6:2.8,3.2:')
     depends_on('py-nose', type='build')
     depends_on('py-setuptools', type='build')
@@ -65,7 +64,8 @@ def setup_dependent_package(self, module, dep_spec):
                 self.spec.version, python_version, arch),
             'numpy/core/include')
 
-    def install(self, spec, prefix):
+    def patch(self):
+        spec = self.spec
         # for build notes see http://www.scipy.org/scipylib/building/linux.html
         lapackblas = LibraryList('')
         if '+lapack' in spec:
@@ -82,5 +82,3 @@ def install(self, spec, prefix):
                 if not ((platform.system() == "Darwin") and
                         (platform.mac_ver()[0] == '10.12')):
                     f.write('rpath=%s\n' % ':'.join(lapackblas.directories))
-
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-openpyxl/package.py b/var/spack/repos/builtin/packages/py-openpyxl/package.py
index b9d8636742..d396916035 100644
--- a/var/spack/repos/builtin/packages/py-openpyxl/package.py
+++ b/var/spack/repos/builtin/packages/py-openpyxl/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyOpenpyxl(Package):
+class PyOpenpyxl(PythonPackage):
     """A Python library to read/write Excel 2007 xlsx/xlsm files"""
 
     homepage = 'http://openpyxl.readthedocs.org/'
@@ -33,10 +33,5 @@ class PyOpenpyxl(Package):
 
     version('2.4.0-a1', 'e5ca6d23ceccb15115d45cdf26e736fc')
 
-    extends('python')
-
     depends_on('py-jdcal', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-ordereddict/package.py b/var/spack/repos/builtin/packages/py-ordereddict/package.py
index 6e038b789c..b560990f00 100644
--- a/var/spack/repos/builtin/packages/py-ordereddict/package.py
+++ b/var/spack/repos/builtin/packages/py-ordereddict/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyOrdereddict(Package):
+class PyOrdereddict(PythonPackage):
     """A drop-in substitute for Py2.7's new collections.
     OrderedDict that works in Python 2.4-2.6."""
 
@@ -33,9 +33,3 @@ class PyOrdereddict(Package):
     url      = "https://pypi.python.org/packages/source/o/ordereddict/ordereddict-1.1.tar.gz"
 
     version('1.1', 'a0ed854ee442051b249bfad0f638bbec')
-
-    extends('python')
-    depends_on('python@2.4:2.6.999')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pandas/package.py b/var/spack/repos/builtin/packages/py-pandas/package.py
index eb2d948f22..c0da33054f 100644
--- a/var/spack/repos/builtin/packages/py-pandas/package.py
+++ b/var/spack/repos/builtin/packages/py-pandas/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPandas(Package):
+class PyPandas(PythonPackage):
     """pandas is a Python package providing fast, flexible, and expressive
        data structures designed to make working with relational or
        labeled data both easy and intuitive. It aims to be the
@@ -42,13 +42,9 @@ class PyPandas(Package):
     version('0.16.1', 'fac4f25748f9610a3e00e765474bdea8')
     version('0.18.0', 'f143762cd7a59815e348adf4308d2cf6')
 
-    extends('python')
     depends_on('py-dateutil', type=('build', 'run'))
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
     depends_on('py-pytz', type=('build', 'run'))
     depends_on('py-numexpr', type=('build', 'run'))
     depends_on('py-bottleneck', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pathlib2/package.py b/var/spack/repos/builtin/packages/py-pathlib2/package.py
index 8db18261b8..8951feff4c 100644
--- a/var/spack/repos/builtin/packages/py-pathlib2/package.py
+++ b/var/spack/repos/builtin/packages/py-pathlib2/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPathlib2(Package):
+class PyPathlib2(PythonPackage):
     """Backport of pathlib from python 3.4"""
 
     homepage = "https://pypi.python.org/pypi/pathlib2"
@@ -33,10 +33,5 @@ class PyPathlib2(Package):
 
     version('2.1.0', '38e4f58b4d69dfcb9edb49a54a8b28d2')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@:3.3.999')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pathspec/package.py b/var/spack/repos/builtin/packages/py-pathspec/package.py
index 399f3c91ef..e5030abc70 100644
--- a/var/spack/repos/builtin/packages/py-pathspec/package.py
+++ b/var/spack/repos/builtin/packages/py-pathspec/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPathspec(Package):
+class PyPathspec(PythonPackage):
     """pathspec extends the test loading and running features of unittest,
     making it easier to write, find and run tests."""
 
@@ -34,8 +34,4 @@ class PyPathspec(Package):
     version('0.3.4', '2a4af9bf2dee98845d583ec61a00d05d',
         url='https://pypi.python.org/packages/14/9d/c9d790d373d6f6938d793e9c549b87ad8670b6fa7fc6176485e6ef11c1a4/pathspec-0.3.4.tar.gz')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pbr/package.py b/var/spack/repos/builtin/packages/py-pbr/package.py
index 0251e436b1..2a320eb76d 100644
--- a/var/spack/repos/builtin/packages/py-pbr/package.py
+++ b/var/spack/repos/builtin/packages/py-pbr/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPbr(Package):
+class PyPbr(PythonPackage):
     """PBR is a library that injects some useful and sensible default
        behaviors into your setuptools run."""
     homepage = "https://pypi.python.org/pypi/pbr"
@@ -33,9 +33,4 @@ class PyPbr(Package):
 
     version('1.8.1', 'c8f9285e1a4ca6f9654c529b158baa3a')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-periodictable/package.py b/var/spack/repos/builtin/packages/py-periodictable/package.py
index d4f732a587..9ff1543018 100644
--- a/var/spack/repos/builtin/packages/py-periodictable/package.py
+++ b/var/spack/repos/builtin/packages/py-periodictable/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPeriodictable(Package):
+class PyPeriodictable(PythonPackage):
     """nose extends the test loading and running features of unittest,
     making it easier to write, find and run tests."""
 
@@ -36,7 +36,3 @@ class PyPeriodictable(Package):
 
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-pyparsing', type=('build', 'run'))
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pexpect/package.py b/var/spack/repos/builtin/packages/py-pexpect/package.py
index 99b69fc30b..5c194c44b6 100644
--- a/var/spack/repos/builtin/packages/py-pexpect/package.py
+++ b/var/spack/repos/builtin/packages/py-pexpect/package.py
@@ -25,16 +25,12 @@
 from spack import *
 
 
-class PyPexpect(Package):
+class PyPexpect(PythonPackage):
     """Pexpect allows easy control of interactive console applications."""
     homepage = "https://pypi.python.org/pypi/pexpect"
     url      = "https://pypi.io/packages/source/p/pexpect/pexpect-4.2.1.tar.gz"
 
     version('4.2.1', '3694410001a99dff83f0b500a1ca1c95')
+    version('3.3', '0de72541d3f1374b795472fed841dce8')
 
-    extends('python')
-    
-    depends_on('py-ptyprocess')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    depends_on('py-ptyprocess', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-phonopy/package.py b/var/spack/repos/builtin/packages/py-phonopy/package.py
index 748f0dd36b..b7f1003e28 100644
--- a/var/spack/repos/builtin/packages/py-phonopy/package.py
+++ b/var/spack/repos/builtin/packages/py-phonopy/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPhonopy(Package):
+class PyPhonopy(PythonPackage):
     """Phonopy is an open source package for phonon
     calculations at harmonic and quasi-harmonic levels."""
     homepage = "http://atztogo.github.io/phonopy/index.html"
@@ -33,11 +33,7 @@ class PyPhonopy(Package):
 
     version('1.10.0', '973ed1bcea46e21b9bf747aab9061ff6')
 
-    extends('python')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-scipy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('py-pyyaml', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--home=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pickleshare/package.py b/var/spack/repos/builtin/packages/py-pickleshare/package.py
index 09a810869d..9bf9ff63fb 100644
--- a/var/spack/repos/builtin/packages/py-pickleshare/package.py
+++ b/var/spack/repos/builtin/packages/py-pickleshare/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPickleshare(Package):
+class PyPickleshare(PythonPackage):
     """Tiny 'shelve'-like database with concurrency support"""
 
     homepage = "https://pypi.python.org/pypi/pickleshare"
@@ -33,9 +33,4 @@ class PyPickleshare(Package):
 
     version('0.7.4', '6a9e5dd8dfc023031f6b7b3f824cab12')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pil/package.py b/var/spack/repos/builtin/packages/py-pil/package.py
index f5d684962d..fb14fb9b27 100644
--- a/var/spack/repos/builtin/packages/py-pil/package.py
+++ b/var/spack/repos/builtin/packages/py-pil/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPil(Package):
+class PyPil(PythonPackage):
     """The Python Imaging Library (PIL) adds image processing capabilities
     to your Python interpreter. This library supports many file formats,
     and provides powerful image processing and graphics capabilities."""
@@ -39,8 +39,4 @@ class PyPil(Package):
 
     # py-pil currently only works with Python2.
     # If you are using Python 3, try using py-pillow instead.
-    extends('python')
     depends_on('python@1.5.2:2.8')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pillow/package.py b/var/spack/repos/builtin/packages/py-pillow/package.py
index 3125a822b4..5729c5c3dd 100644
--- a/var/spack/repos/builtin/packages/py-pillow/package.py
+++ b/var/spack/repos/builtin/packages/py-pillow/package.py
@@ -23,9 +23,10 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 from spack import *
+import sys
 
 
-class PyPillow(Package):
+class PyPillow(PythonPackage):
     """Pillow is a fork of the Python Imaging Library (PIL). It adds image
     processing capabilities to your Python interpreter. This library supports
     many file formats, and provides powerful image processing and graphics
@@ -64,8 +65,7 @@ class PyPillow(Package):
     #         description='Provide improved color quantization')
 
     # Required dependencies
-    extends('python')
-    depends_on('binutils', type='build')
+    depends_on('binutils', type='build', when=sys.platform != 'darwin')
     depends_on('py-setuptools', type='build')
 
     # Recommended dependencies
@@ -83,6 +83,8 @@ class PyPillow(Package):
     # depends_on('webpmux', when='+webpmux')
     # depends_on('imagequant', when='+imagequant')
 
+    phases = ['build_ext', 'install']
+
     def patch(self):
         """Patch setup.py to provide lib and include directories
         for dependencies."""
@@ -121,13 +123,10 @@ def patch(self):
                              spec['openjpeg'].prefix.lib,
                              spec['openjpeg'].prefix.include))
 
-    def install(self, spec, prefix):
+    def build_ext_args(self, spec, prefix):
         def variant_to_flag(variant):
             able = 'enable' if '+{0}'.format(variant) in spec else 'disable'
             return '--{0}-{1}'.format(able, variant)
 
         variants = ['jpeg', 'zlib', 'tiff', 'freetype', 'lcms', 'jpeg2000']
-        build_args = list(map(variant_to_flag, variants))
-
-        setup_py('build_ext', *build_args)
-        setup_py('install', '--prefix={0}'.format(prefix))
+        return list(map(variant_to_flag, variants))
diff --git a/var/spack/repos/builtin/packages/py-pip/package.py b/var/spack/repos/builtin/packages/py-pip/package.py
index dca73f763f..73e1661245 100644
--- a/var/spack/repos/builtin/packages/py-pip/package.py
+++ b/var/spack/repos/builtin/packages/py-pip/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPip(Package):
+class PyPip(PythonPackage):
     """The PyPA recommended tool for installing Python packages."""
 
     homepage = "https://pypi.python.org/pypi/pip"
@@ -33,9 +33,4 @@ class PyPip(Package):
 
     version('9.0.1', '35f01da33009719497f01a4ba69d63c9')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-ply/package.py b/var/spack/repos/builtin/packages/py-ply/package.py
index d249de64fa..f5a1e537e2 100644
--- a/var/spack/repos/builtin/packages/py-ply/package.py
+++ b/var/spack/repos/builtin/packages/py-ply/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyPly(Package):
+class PyPly(PythonPackage):
     """PLY is nothing more than a straightforward lex/yacc implementation."""
     homepage = "http://www.dabeaz.com/ply"
     url      = "http://www.dabeaz.com/ply/ply-3.8.tar.gz"
 
     version('3.8', '94726411496c52c87c2b9429b12d5c50')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pmw/package.py b/var/spack/repos/builtin/packages/py-pmw/package.py
index 5173864f62..3293d94cd6 100644
--- a/var/spack/repos/builtin/packages/py-pmw/package.py
+++ b/var/spack/repos/builtin/packages/py-pmw/package.py
@@ -25,15 +25,10 @@
 from spack import *
 
 
-class PyPmw(Package):
+class PyPmw(PythonPackage):
     """Pmw is a toolkit for building high-level compound widgets, or
        megawidgets, constructed using other widgets as component parts."""
     homepage = "https://pypi.python.org/pypi/Pmw"
     url      = "https://pypi.python.org/packages/source/P/Pmw/Pmw-2.0.0.tar.gz"
 
     version('2.0.0', 'c7c3f26c4f5abaa99807edefee578fc0')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-prettytable/package.py b/var/spack/repos/builtin/packages/py-prettytable/package.py
index 55a323a0d6..2203f68af0 100644
--- a/var/spack/repos/builtin/packages/py-prettytable/package.py
+++ b/var/spack/repos/builtin/packages/py-prettytable/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPrettytable(Package):
+class PyPrettytable(PythonPackage):
     """PrettyTable is a simple Python library designed to make
     it quick and easy to represent tabular data in visually
     appealing ASCII tables.
@@ -36,8 +36,4 @@ class PyPrettytable(Package):
 
     version('0.7.2', 'a6b80afeef286ce66733d54a0296b13b')
 
-    extends("python")
     depends_on("py-setuptools", type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-proj/package.py b/var/spack/repos/builtin/packages/py-proj/package.py
index a839a1d0b6..949aab88c3 100644
--- a/var/spack/repos/builtin/packages/py-proj/package.py
+++ b/var/spack/repos/builtin/packages/py-proj/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyProj(Package):
+class PyProj(PythonPackage):
     """Python interface to the PROJ.4 Library."""
     homepage = "http://jswhit.github.io/pyproj/"
     url      = "https://github.com/jswhit/pyproj/tarball/v1.9.5.1rel"
@@ -37,8 +37,6 @@ class PyProj(Package):
 
     version('1.9.5.1', 'a4b80d7170fc82aee363d7f980279835')
 
-    extends('python')
-
     depends_on('py-cython', type='build')
     depends_on('py-setuptools', type='build')
 
@@ -46,6 +44,3 @@ class PyProj(Package):
     # The py-proj git repo actually includes the correct version of PROJ.4,
     # which is built internally as part of the py-proj build.
     # Adding depends_on('proj') will cause mysterious build errors.
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-prompt-toolkit/package.py b/var/spack/repos/builtin/packages/py-prompt-toolkit/package.py
index 5c81cf1701..da48cb932f 100644
--- a/var/spack/repos/builtin/packages/py-prompt-toolkit/package.py
+++ b/var/spack/repos/builtin/packages/py-prompt-toolkit/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPromptToolkit(Package):
+class PyPromptToolkit(PythonPackage):
     """Library for building powerful interactive command lines in Python"""
 
     homepage = "https://pypi.python.org/pypi/prompt_toolkit"
@@ -33,11 +33,6 @@ class PyPromptToolkit(Package):
 
     version('1.0.9', 'a39f91a54308fb7446b1a421c11f227c')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-    depends_on('py-six@1.9.0:')
-    depends_on('py-wcwidth')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-six@1.9.0:', type=('build', 'run'))
+    depends_on('py-wcwidth', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-protobuf/package.py b/var/spack/repos/builtin/packages/py-protobuf/package.py
index d1186775bb..4cdb3801a5 100644
--- a/var/spack/repos/builtin/packages/py-protobuf/package.py
+++ b/var/spack/repos/builtin/packages/py-protobuf/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyProtobuf(Package):
+class PyProtobuf(PythonPackage):
     """Protocol buffers are Google's language-neutral, platform-neutral,
     extensible mechanism for serializing structured data - think XML, but
     smaller, faster, and simpler. You define how you want your data to be
@@ -42,9 +42,4 @@ class PyProtobuf(Package):
     version('2.4.1', '72f5141d20ab1bcae6b1e00acfb1068a')
     version('2.3.0', 'bb020c962f252fe81bfda8fb433bafdd')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-ptyprocess/package.py b/var/spack/repos/builtin/packages/py-ptyprocess/package.py
index fc65e617ba..2419793227 100644
--- a/var/spack/repos/builtin/packages/py-ptyprocess/package.py
+++ b/var/spack/repos/builtin/packages/py-ptyprocess/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPtyprocess(Package):
+class PyPtyprocess(PythonPackage):
     """Run a subprocess in a pseudo terminal"""
 
     homepage = "https://pypi.python.org/pypi/ptyprocess"
@@ -33,9 +33,4 @@ class PyPtyprocess(Package):
 
     version('0.5.1', '94e537122914cc9ec9c1eadcd36e73a1')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pudb/package.py b/var/spack/repos/builtin/packages/py-pudb/package.py
index d9c2d3e0a4..66896bd83f 100644
--- a/var/spack/repos/builtin/packages/py-pudb/package.py
+++ b/var/spack/repos/builtin/packages/py-pudb/package.py
@@ -26,7 +26,7 @@
 from spack import *
 
 
-class PyPudb(Package):
+class PyPudb(PythonPackage):
     """Full-screen console debugger for Python"""
 
     homepage = "http://mathema.tician.de/software/pudb"
@@ -34,11 +34,6 @@ class PyPudb(Package):
 
     version('2016.2', '4573b70163329c1cb59836a357bfdf7c')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-urwid@1.1.1:', type=('build', 'run'))
     depends_on('py-pygments@1.0:', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-py/package.py b/var/spack/repos/builtin/packages/py-py/package.py
index f624aaf389..5a963105bd 100644
--- a/var/spack/repos/builtin/packages/py-py/package.py
+++ b/var/spack/repos/builtin/packages/py-py/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPy(Package):
+class PyPy(PythonPackage):
     """library with cross-python path, ini-parsing, io, code, log facilities"""
 
     homepage = "http://pylib.readthedocs.io/en/latest/"
@@ -33,9 +33,4 @@ class PyPy(Package):
 
     version('1.4.31', '5d2c63c56dc3f2115ec35c066ecd582b')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-py2cairo/package.py b/var/spack/repos/builtin/packages/py-py2cairo/package.py
index efc3645745..bb404c61f0 100644
--- a/var/spack/repos/builtin/packages/py-py2cairo/package.py
+++ b/var/spack/repos/builtin/packages/py-py2cairo/package.py
@@ -26,19 +26,19 @@
 
 
 class PyPy2cairo(Package):
-    """bindings for the Cairo for Python 2,
-       to be used in Python."""
+    """Pycairo is a set of Python bindings for the cairo graphics library."""
 
-    homepage = "https://pypi.python.org/pypi/pycairo"
+    homepage = "https://www.cairographics.org/pycairo/"
     url      = "https://cairographics.org/releases/py2cairo-1.10.0.tar.bz2"
 
     version('1.10.0', '20337132c4ab06c1146ad384d55372c5')
 
     extends('python')
-    depends_on("cairo")
-    depends_on("pixman")
+
+    depends_on('cairo+X')
+    depends_on('pixman')
 
     def install(self, spec, prefix):
-        python('waf', 'configure', '--prefix=%s' % prefix)
+        python('waf', 'configure', '--prefix={0}'.format(prefix))
         python('waf', 'build')
         python('waf', 'install')
diff --git a/var/spack/repos/builtin/packages/py-py2neo/package.py b/var/spack/repos/builtin/packages/py-py2neo/package.py
index aed0859021..1db080ac97 100644
--- a/var/spack/repos/builtin/packages/py-py2neo/package.py
+++ b/var/spack/repos/builtin/packages/py-py2neo/package.py
@@ -22,11 +22,10 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import depends_on, extends, version
-from spack import Package
+from spack import *
 
 
-class PyPy2neo(Package):
+class PyPy2neo(PythonPackage):
     """Py2neo is a client library and toolkit for working with Neo4j from
     within Python applications and from the command line."""
 
@@ -40,7 +39,3 @@ class PyPy2neo(Package):
     version('2.0.4', 'b3f7efd3344dc3f66db4eda11e5899f7')
 
     depends_on("py-setuptools", type='build')
-    extends("python")
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pychecker/package.py b/var/spack/repos/builtin/packages/py-pychecker/package.py
index b1f8aad33f..de09b380c9 100644
--- a/var/spack/repos/builtin/packages/py-pychecker/package.py
+++ b/var/spack/repos/builtin/packages/py-pychecker/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyPychecker(Package):
-    """"""
+class PyPychecker(PythonPackage):
+    """Python source code checking tool."""
     homepage = "http://pychecker.sourceforge.net/"
     url      = "http://sourceforge.net/projects/pychecker/files/pychecker/0.8.19/pychecker-0.8.19.tar.gz"
 
     version('0.8.19', 'c37182863dfb09209d6ba4f38fce9d2b')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pycodestyle/package.py b/var/spack/repos/builtin/packages/py-pycodestyle/package.py
index 9feb5eb8f6..3e668a2704 100644
--- a/var/spack/repos/builtin/packages/py-pycodestyle/package.py
+++ b/var/spack/repos/builtin/packages/py-pycodestyle/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPycodestyle(Package):
+class PyPycodestyle(PythonPackage):
     """pycodestyle is a tool to check your Python code against some of the
     style conventions in PEP 8. Note: formerly called pep8."""
 
@@ -42,11 +42,6 @@ class PyPycodestyle(Package):
     version('1.5.5', 'cfa12df9b86b3a1dfb13aced1927e12f')
     version('1.5.4', '3977a760829652543544074c684610ee')
 
-    extends('python')
-
     # Most Python packages only require py-setuptools as a build dependency.
     # However, py-pycodestyle requires py-setuptools during runtime as well.
     depends_on('py-setuptools', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pycparser/package.py b/var/spack/repos/builtin/packages/py-pycparser/package.py
index e7b91f4495..8de5c39d32 100644
--- a/var/spack/repos/builtin/packages/py-pycparser/package.py
+++ b/var/spack/repos/builtin/packages/py-pycparser/package.py
@@ -25,15 +25,11 @@
 from spack import *
 
 
-class PyPycparser(Package):
+class PyPycparser(PythonPackage):
     """A complete parser of the C language, written in pure python."""
     homepage = "https://github.com/eliben/pycparser"
     url      = "https://pypi.python.org/packages/source/p/pycparser/pycparser-2.13.tar.gz"
 
     version('2.13', 'e4fe1a2d341b22e25da0d22f034ef32f')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pycurl/package.py b/var/spack/repos/builtin/packages/py-pycurl/package.py
index ab62440f8e..81a2a35064 100644
--- a/var/spack/repos/builtin/packages/py-pycurl/package.py
+++ b/var/spack/repos/builtin/packages/py-pycurl/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPycurl(Package):
+class PyPycurl(PythonPackage):
     """PycURL is a Python interface to libcurl. PycURL can be used to fetch
     objects identified by a URL from a Python program."""
 
@@ -34,10 +34,6 @@ class PyPycurl(Package):
 
     version('7.43.0', 'c94bdba01da6004fa38325e9bd6b9760')
 
-    extends('python')
     depends_on('python@2.6:')
     depends_on('py-setuptools', type='build')
     depends_on('curl@7.19.0:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pydatalog/package.py b/var/spack/repos/builtin/packages/py-pydatalog/package.py
index b2203ae04c..600a34cdfc 100644
--- a/var/spack/repos/builtin/packages/py-pydatalog/package.py
+++ b/var/spack/repos/builtin/packages/py-pydatalog/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyPydatalog(Package):
+class PyPydatalog(PythonPackage):
     """pyDatalog adds logic programming to Python."""
     homepage = 'https://pypi.python.org/pypi/pyDatalog/'
     url      = 'https://pypi.python.org/packages/09/0b/2670eb9c0027aacfb5b5024ca75e5fee2f1261180ab8797108ffc941158a/pyDatalog-0.17.1.zip'
 
     version('0.17.1', '6b2682301200068d208d6f2d01723939')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pyelftools/package.py b/var/spack/repos/builtin/packages/py-pyelftools/package.py
index 96a5645541..d586f14f0d 100644
--- a/var/spack/repos/builtin/packages/py-pyelftools/package.py
+++ b/var/spack/repos/builtin/packages/py-pyelftools/package.py
@@ -25,15 +25,10 @@
 from spack import *
 
 
-class PyPyelftools(Package):
+class PyPyelftools(PythonPackage):
     """A pure-Python library for parsing and analyzing ELF files and DWARF
        debugging information"""
     homepage = "https://pypi.python.org/pypi/pyelftools"
     url      = "https://pypi.python.org/packages/source/p/pyelftools/pyelftools-0.23.tar.gz"
 
     version('0.23', 'aa7cefa8bd2f63d7b017440c9084f310')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pyflakes/package.py b/var/spack/repos/builtin/packages/py-pyflakes/package.py
index fc904d5334..53674bb6e4 100644
--- a/var/spack/repos/builtin/packages/py-pyflakes/package.py
+++ b/var/spack/repos/builtin/packages/py-pyflakes/package.py
@@ -25,8 +25,8 @@
 from spack import *
 
 
-class PyPyflakes(Package):
-    """A simple program which checks Python source files for errors.."""
+class PyPyflakes(PythonPackage):
+    """A simple program which checks Python source files for errors."""
 
     homepage = "https://github.com/PyCQA/pyflakes"
     url      = "https://github.com/PyCQA/pyflakes/archive/1.3.0.tar.gz"
@@ -42,11 +42,6 @@ class PyPyflakes(Package):
     version('0.9.1', '8108d2248e93ca6a315fa2dd31ee9bb1')
     version('0.9.0', '43c2bcee88606bde55dbf25a253ef886')
 
-    extends('python')
-
     # Most Python packages only require py-setuptools as a build dependency.
     # However, py-pyflakes requires py-setuptools during runtime as well.
     depends_on('py-setuptools', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pygments/package.py b/var/spack/repos/builtin/packages/py-pygments/package.py
index c61b080e14..42e3366cdf 100644
--- a/var/spack/repos/builtin/packages/py-pygments/package.py
+++ b/var/spack/repos/builtin/packages/py-pygments/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPygments(Package):
+class PyPygments(PythonPackage):
     """Pygments is a syntax highlighting package written in Python."""
 
     homepage = "https://pypi.python.org/pypi/pygments"
@@ -35,9 +35,4 @@ class PyPygments(Package):
     version('2.0.1', 'e0daf4c14a4fe5b630da765904de4d6c')
     version('2.0.2', '238587a1370d62405edabd0794b3ec4a')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pygobject/package.py b/var/spack/repos/builtin/packages/py-pygobject/package.py
index 675eb8f004..1dae1a128b 100644
--- a/var/spack/repos/builtin/packages/py-pygobject/package.py
+++ b/var/spack/repos/builtin/packages/py-pygobject/package.py
@@ -30,6 +30,8 @@ class PyPygobject(AutotoolsPackage):
        to be used in Python."""
 
     homepage = "https://pypi.python.org/pypi/pygobject"
+
+    # FIXME: This URL is no longer available for download from PyPi
     url      = "https://pypi.python.org/packages/6d/15/97c8b5ccca2be14cf59a2f79e15e3a82a1c3408a6b76b4107689a8b94846/pygobject-2.28.3.tar.bz2"
 
     version('2.28.3', 'aa64900b274c4661a5c32e52922977f9')
diff --git a/var/spack/repos/builtin/packages/py-pylint/package.py b/var/spack/repos/builtin/packages/py-pylint/package.py
index c0c31a7686..84830c9bcf 100644
--- a/var/spack/repos/builtin/packages/py-pylint/package.py
+++ b/var/spack/repos/builtin/packages/py-pylint/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPylint(Package):
+class PyPylint(PythonPackage):
     """array processing for numbers, strings, records, and objects."""
     homepage = "https://pypi.python.org/pypi/pylint"
     url      = "https://pypi.python.org/packages/source/p/pylint/pylint-1.4.1.tar.gz"
@@ -39,6 +39,3 @@ class PyPylint(Package):
     depends_on('py-logilab-common', type=('build', 'run'))
     depends_on('py-nose', type='build')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pypar/package.py b/var/spack/repos/builtin/packages/py-pypar/package.py
index ffbc232353..f10b6d807f 100644
--- a/var/spack/repos/builtin/packages/py-pypar/package.py
+++ b/var/spack/repos/builtin/packages/py-pypar/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPypar(Package):
+class PyPypar(PythonPackage):
     """Pypar is an efficient but easy-to-use module that allows programs
        written in Python to run in parallel on multiple processors and
        communicate using MPI."""
@@ -34,13 +34,11 @@ class PyPypar(Package):
 
     version('2.1.5_108', '7a1f28327d2a3b679f9455c843d850b8')
 
-    extends('python')
     depends_on('mpi')
-    depends_on('py-numpy')
-
-    def install(self, spec, prefix):
-        with working_dir('source'):
-            setup_py('install', '--prefix=%s' % prefix)
+    depends_on('py-numpy', type=('build', 'run'))
 
     def url_for_version(self, version):
         return "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/pypar/pypar-%s.tgz" % version
+
+    def build_directory(self):
+        return 'source'
diff --git a/var/spack/repos/builtin/packages/py-pyparsing/package.py b/var/spack/repos/builtin/packages/py-pyparsing/package.py
index c3b4432d33..cb158a76d6 100644
--- a/var/spack/repos/builtin/packages/py-pyparsing/package.py
+++ b/var/spack/repos/builtin/packages/py-pyparsing/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyPyparsing(Package):
+class PyPyparsing(PythonPackage):
     """A Python Parsing Module."""
     homepage = "https://pypi.python.org/pypi/pyparsing"
     url      = "https://pypi.python.org/packages/source/p/pyparsing/pyparsing-2.0.3.tar.gz"
 
     version('2.0.3', '0fe479be09fc2cf005f753d3acc35939')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pyside/package.py b/var/spack/repos/builtin/packages/py-pyside/package.py
index ab8c70140c..961aef7864 100644
--- a/var/spack/repos/builtin/packages/py-pyside/package.py
+++ b/var/spack/repos/builtin/packages/py-pyside/package.py
@@ -26,7 +26,7 @@
 import os
 
 
-class PyPyside(Package):
+class PyPyside(PythonPackage):
     """Python bindings for Qt."""
     homepage = "https://pypi.python.org/pypi/pyside"
     url      = "https://pypi.python.org/packages/source/P/PySide/PySide-1.2.2.tar.gz"
@@ -36,7 +36,6 @@ class PyPyside(Package):
 
     depends_on('cmake', type='build')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
     depends_on('py-sphinx', type=('build', 'run'))
     depends_on('qt@4.5:4.9')
@@ -80,5 +79,5 @@ def patch(self):
                     "'Programming Language :: Python :: 3.5'",
                     "setup.py")
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix, '--jobs=%s' % make_jobs)
+    def build_args(self, spec, prefix):
+        return ['--jobs={0}'.format(make_jobs)]
diff --git a/var/spack/repos/builtin/packages/py-pytables/package.py b/var/spack/repos/builtin/packages/py-pytables/package.py
index 110d13f355..3d9bfb2c2f 100644
--- a/var/spack/repos/builtin/packages/py-pytables/package.py
+++ b/var/spack/repos/builtin/packages/py-pytables/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPytables(Package):
+class PyPytables(PythonPackage):
     """PyTables is a package for managing hierarchical datasets and designed to
     efficiently and easily cope with extremely large amounts of data."""
     homepage = "http://www.pytables.org/"
@@ -35,13 +35,12 @@ class PyPytables(Package):
             url='https://github.com/PyTables/PyTables/archive/v3.3.0.tar.gz')
     version('3.2.2', '7cbb0972e4d6580f629996a5bed92441')
 
-    extends('python')
-    depends_on('hdf5')
-    depends_on('py-numpy', type=('build', 'run'))
-    depends_on('py-numexpr', type=('build', 'run'))
+    depends_on('hdf5@1.8.0:1.8.999')
+    depends_on('py-numpy@1.8.0:', type=('build', 'run'))
+    depends_on('py-numexpr@2.5.2:', type=('build', 'run'))
     depends_on('py-cython', type=('build', 'run'))
+    depends_on('py-six', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
 
-    def install(self, spec, prefix):
-        env["HDF5_DIR"] = spec['hdf5'].prefix
-        setup_py('install', '--prefix=%s' % prefix)
+    def setup_environment(self, spack_env, run_env):
+        spack_env.set('HDF5_DIR', self.spec['hdf5'].prefix)
diff --git a/var/spack/repos/builtin/packages/py-pytest/package.py b/var/spack/repos/builtin/packages/py-pytest/package.py
index 1ad356ff4b..4af49306c2 100644
--- a/var/spack/repos/builtin/packages/py-pytest/package.py
+++ b/var/spack/repos/builtin/packages/py-pytest/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPytest(Package):
+class PyPytest(PythonPackage):
     """pytest: simple powerful testing with Python."""
 
     homepage = "http://doc.pytest.org/en/latest/"
@@ -34,10 +34,5 @@ class PyPytest(Package):
     version('3.0.2', '61dc36e65a6f6c11c53b1388e043a9f5',
             url="https://pypi.python.org/packages/2b/05/e20806c99afaff43331f5fd8770bb346145303882f98ef3275fa1dd66f6d/pytest-3.0.2.tar.gz")
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-py@1.4.29:', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-python-daemon/package.py b/var/spack/repos/builtin/packages/py-python-daemon/package.py
index f8532c9175..9ed085f031 100644
--- a/var/spack/repos/builtin/packages/py-python-daemon/package.py
+++ b/var/spack/repos/builtin/packages/py-python-daemon/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPythonDaemon(Package):
+class PyPythonDaemon(PythonPackage):
     """Library to implement a well-behaved Unix daemon process.
 
        This library implements the well-behaved daemon specification of
@@ -42,9 +42,5 @@ class PyPythonDaemon(Package):
 
     version('2.0.5', '73e7f49f525c51fa4a995aea4d80de41')
 
-    extends("python")
     depends_on("py-setuptools", type='build')
     depends_on("py-lockfile", type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-pytz/package.py b/var/spack/repos/builtin/packages/py-pytz/package.py
index 486b86a467..7a905f9f98 100644
--- a/var/spack/repos/builtin/packages/py-pytz/package.py
+++ b/var/spack/repos/builtin/packages/py-pytz/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyPytz(Package):
+class PyPytz(PythonPackage):
     """World timezone definitions, modern and historical."""
 
     homepage = "https://pypi.python.org/pypi/pytz"
@@ -37,9 +37,4 @@ class PyPytz(Package):
     version('2015.4', '417a47b1c432d90333e42084a605d3d8')
     version('2016.3', 'abae92c3301b27bd8a9f56b14f52cb29')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-pyyaml/package.py b/var/spack/repos/builtin/packages/py-pyyaml/package.py
index d760fcaae9..94d8fdd0e6 100644
--- a/var/spack/repos/builtin/packages/py-pyyaml/package.py
+++ b/var/spack/repos/builtin/packages/py-pyyaml/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class PyPyyaml(Package):
+class PyPyyaml(PythonPackage):
     """PyYAML is a YAML parser and emitter for Python."""
     homepage = "http://pyyaml.org/wiki/PyYAML"
     url      = "http://pyyaml.org/download/pyyaml/PyYAML-3.11.tar.gz"
 
     version('3.11', 'f50e08ef0fe55178479d3a618efe21db')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-readme-renderer/package.py b/var/spack/repos/builtin/packages/py-readme-renderer/package.py
new file mode 100644
index 0000000000..2abe4bac74
--- /dev/null
+++ b/var/spack/repos/builtin/packages/py-readme-renderer/package.py
@@ -0,0 +1,42 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class PyReadmeRenderer(PythonPackage):
+    """readme_renderer is a library for rendering "readme" descriptions
+    for Warehouse."""
+
+    homepage = "https://github.com/pypa/readme_renderer"
+    url      = "https://pypi.python.org/packages/f2/6e/ef1bc3a24eb14e14574aba9dc1bd50bc9a5e7cc880e8ff9cadd385b4fb37/readme_renderer-16.0.tar.gz"
+
+    version('16.0', '70321cea986956bcf2deef9981569f39')
+
+    depends_on('python@2.6:2.7,3.2:3.3')
+    depends_on('py-setuptools', type='build')
+    depends_on('py-bleach', type=('build', 'run'))
+    depends_on('py-docutils@0.13.1:', type=('build', 'run'))
+    depends_on('py-pygments', type=('build', 'run'))
+    depends_on('py-six', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-restview/package.py b/var/spack/repos/builtin/packages/py-restview/package.py
index 1aaf5d8f8a..871016fdb1 100644
--- a/var/spack/repos/builtin/packages/py-restview/package.py
+++ b/var/spack/repos/builtin/packages/py-restview/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyRestview(Package):
+class PyRestview(PythonPackage):
     """A viewer for ReStructuredText documents that renders them on the fly."""
 
     homepage = "https://mg.pov.lt/restview/"
@@ -33,9 +33,7 @@ class PyRestview(Package):
 
     version('2.6.1', 'ac8b70e15b8f1732d1733d674813666b')
 
-    extends('python')
-    depends_on('py-docutils', type=('build', 'run'))
+    depends_on('python@2.7.0:2.7.999,3.3:3.5')
+    depends_on('py-docutils@0.13.1:', type=('build', 'run'))
+    depends_on('py-readme-renderer', type=('build', 'run'))
     depends_on('py-pygments', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-rpy2/package.py b/var/spack/repos/builtin/packages/py-rpy2/package.py
index 6308afd3dd..284a41894a 100644
--- a/var/spack/repos/builtin/packages/py-rpy2/package.py
+++ b/var/spack/repos/builtin/packages/py-rpy2/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyRpy2(Package):
+class PyRpy2(PythonPackage):
     """rpy2 is a redesign and rewrite of rpy. It is providing a low-level
        interface to R from Python, a proposed high-level interface,
        including wrappers to graphical libraries, as well as R-like
@@ -38,11 +38,11 @@ class PyRpy2(Package):
     version('2.5.4', '115a20ac30883f096da2bdfcab55196d')
     version('2.5.6', 'a36e758b633ce6aec6a5f450bfee980f')
 
-    extends('python')
+    # FIXME: Missing dependencies:
+    # ld: cannot find -licuuc
+    # ld: cannot find -licui18
+
     depends_on('py-six', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
 
-    depends_on('R')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    depends_on('r')
diff --git a/var/spack/repos/builtin/packages/py-rtree/package.py b/var/spack/repos/builtin/packages/py-rtree/package.py
index dfb69f6983..55f98ad19e 100644
--- a/var/spack/repos/builtin/packages/py-rtree/package.py
+++ b/var/spack/repos/builtin/packages/py-rtree/package.py
@@ -23,10 +23,9 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 from spack import *
-import os
 
 
-class PyRtree(Package):
+class PyRtree(PythonPackage):
     """Python interface to the RTREE.4 Library."""
     homepage = "http://toblerity.org/rtree/"
     url      = "https://github.com/Toblerity/rtree/tarball/0.8.2"
@@ -46,16 +45,12 @@ class PyRtree(Package):
     # Does not work with Spack
     # version('0.8.2', '593c7ac6babc397b8ba58f1636c1e0a0')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('libspatialindex')
 
-    def install(self, spec, prefix):
-        lib = os.path.join(spec['libspatialindex'].prefix, 'lib')
-        os.environ['SPATIALINDEX_LIBRARY'] = \
-            os.path.join(lib, 'libspatialindex.%s' % dso_suffix)
-        os.environ['SPATIALINDEX_C_LIBRARY'] = \
-            os.path.join(lib, 'libspatialindex_c.%s' % dso_suffix)
-
-        setup_py('install', '--prefix=%s' % prefix)
+    def setup_environment(self, spack_env, run_env):
+        lib = self.spec['libspatialindex'].prefix.lib
+        spack_env.set('SPATIALINDEX_LIBRARY',
+                      join_path(lib, 'libspatialindex.%s'   % dso_suffix))
+        spack_env.set('SPATIALINDEX_C_LIBRARY',
+                      join_path(lib, 'libspatialindex_c.%s' % dso_suffix))
diff --git a/var/spack/repos/builtin/packages/py-scientificpython/package.py b/var/spack/repos/builtin/packages/py-scientificpython/package.py
index 48b56ef2fa..f0fe2e4e6c 100644
--- a/var/spack/repos/builtin/packages/py-scientificpython/package.py
+++ b/var/spack/repos/builtin/packages/py-scientificpython/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyScientificpython(Package):
+class PyScientificpython(PythonPackage):
     """ScientificPython is a collection of Python modules for
        scientific computing. It contains support for geometry,
        mathematical functions, statistics, physical units, IO,
@@ -36,8 +36,3 @@ class PyScientificpython(Package):
     version('2.8.1', '73ee0df19c7b58cdf2954261f0763c77')
 
     depends_on('py-numpy')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-scikit-image/package.py b/var/spack/repos/builtin/packages/py-scikit-image/package.py
index 9ea21bdbdf..d05341f9eb 100644
--- a/var/spack/repos/builtin/packages/py-scikit-image/package.py
+++ b/var/spack/repos/builtin/packages/py-scikit-image/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyScikitImage(Package):
+class PyScikitImage(PythonPackage):
     """Image processing algorithms for SciPy, including IO, morphology,
     filtering, warping, color manipulation, object detection, etc."""
 
@@ -43,6 +43,3 @@ class PyScikitImage(Package):
     depends_on('py-scipy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-scikit-learn/package.py b/var/spack/repos/builtin/packages/py-scikit-learn/package.py
index 4a713e6565..7c7102ce41 100644
--- a/var/spack/repos/builtin/packages/py-scikit-learn/package.py
+++ b/var/spack/repos/builtin/packages/py-scikit-learn/package.py
@@ -25,8 +25,8 @@
 from spack import *
 
 
-class PyScikitLearn(Package):
-    """"""
+class PyScikitLearn(PythonPackage):
+    """A set of python modules for machine learning and data mining."""
     homepage = "https://pypi.python.org/pypi/scikit-learn"
     url      = "https://pypi.python.org/packages/source/s/scikit-learn/scikit-learn-0.15.2.tar.gz"
 
@@ -34,11 +34,6 @@ class PyScikitLearn(Package):
     version('0.16.1', '363ddda501e3b6b61726aa40b8dbdb7e')
     version('0.17.1', 'a2f8b877e6d99b1ed737144f5a478dfc')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-scipy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-scipy/package.py b/var/spack/repos/builtin/packages/py-scipy/package.py
index 509a1f8b94..85b6f631e1 100644
--- a/var/spack/repos/builtin/packages/py-scipy/package.py
+++ b/var/spack/repos/builtin/packages/py-scipy/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyScipy(Package):
+class PyScipy(PythonPackage):
     """SciPy (pronounced "Sigh Pie") is a Scientific Library for Python.
     It provides many user-friendly and efficient numerical routines such
     as routines for numerical integration and optimization."""
@@ -39,16 +39,13 @@ class PyScipy(Package):
     version('0.15.1', 'be56cd8e60591d6332aac792a5880110')
     version('0.15.0', '639112f077f0aeb6d80718dc5019dc7a')
 
-    extends('python')
     depends_on('python@2.6:2.8,3.2:')
     depends_on('py-nose', type='build')
     # Known not to work with 2.23, 2.25
     depends_on('binutils@2.26:', type='build')
     depends_on('py-numpy@1.7.1:+blas+lapack', type=('build', 'run'))
+
+    # NOTE: scipy picks up Blas/Lapack from numpy, see
+    # http://www.scipy.org/scipylib/building/linux.html#step-4-build-numpy-1-5-0
     depends_on('blas')
     depends_on('lapack')
-
-    def install(self, spec, prefix):
-        # NOTE: scipy picks up Blas/Lapack from numpy, see
-        # http://www.scipy.org/scipylib/building/linux.html#step-4-build-numpy-1-5-0
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-setuptools/package.py b/var/spack/repos/builtin/packages/py-setuptools/package.py
index d696fdf776..d355800958 100644
--- a/var/spack/repos/builtin/packages/py-setuptools/package.py
+++ b/var/spack/repos/builtin/packages/py-setuptools/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySetuptools(Package):
+class PySetuptools(PythonPackage):
     """A Python utility that aids in the process of downloading, building,
        upgrading, installing, and uninstalling Python packages."""
 
@@ -41,8 +41,3 @@ class PySetuptools(Package):
     version('18.1', 'f72e87f34fbf07f299f6cb46256a0b06')
     version('16.0', '0ace0b96233516fc5f7c857d086aa3ad')
     version('11.3.1', '01f69212e019a2420c1693fb43593930')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-shiboken/package.py b/var/spack/repos/builtin/packages/py-shiboken/package.py
index c6c23acd33..3ad51d5fc5 100644
--- a/var/spack/repos/builtin/packages/py-shiboken/package.py
+++ b/var/spack/repos/builtin/packages/py-shiboken/package.py
@@ -26,7 +26,7 @@
 import os
 
 
-class PyShiboken(Package):
+class PyShiboken(PythonPackage):
     """Shiboken generates bindings for C++ libraries using CPython."""
     homepage = "https://shiboken.readthedocs.org/"
     url      = "https://pypi.python.org/packages/source/S/Shiboken/Shiboken-1.2.2.tar.gz"
@@ -35,8 +35,8 @@ class PyShiboken(Package):
 
     depends_on('cmake', type='build')
 
-    extends('python')
     depends_on("py-setuptools", type='build')
+    depends_on("py-sphinx", type=('build', 'run'))
     depends_on("libxml2")
     depends_on("qt@:4.8")
 
@@ -63,5 +63,5 @@ def patch(self):
             r'#rpath_cmd(shiboken_path, srcpath)',
             'shiboken_postinstall.py')
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix, '--jobs=%s' % make_jobs)
+    def build_args(self, spec, prefix):
+        return ['--jobs={0}'.format(make_jobs)]
diff --git a/var/spack/repos/builtin/packages/py-simplegeneric/package.py b/var/spack/repos/builtin/packages/py-simplegeneric/package.py
index b2ee72822b..3881f8bc88 100644
--- a/var/spack/repos/builtin/packages/py-simplegeneric/package.py
+++ b/var/spack/repos/builtin/packages/py-simplegeneric/package.py
@@ -25,8 +25,8 @@
 from spack import *
 
 
-class PySimplegeneric(Package):
-    """Simple generic functions (similar to Python's own len(), 
+class PySimplegeneric(PythonPackage):
+    """Simple generic functions (similar to Python's own len(),
     pickle.dump(), etc.)"""
 
     homepage = "https://pypi.python.org/pypi/simplegeneric"
@@ -35,9 +35,4 @@ class PySimplegeneric(Package):
     version('0.8.1', 'f9c1fab00fd981be588fc32759f474e3')
     version('0.8', 'eaa358a5f9517a8b475d03fbee3ec90f')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-singledispatch/package.py b/var/spack/repos/builtin/packages/py-singledispatch/package.py
index 05d8c16b44..9125ecb5f8 100644
--- a/var/spack/repos/builtin/packages/py-singledispatch/package.py
+++ b/var/spack/repos/builtin/packages/py-singledispatch/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySingledispatch(Package):
+class PySingledispatch(PythonPackage):
     """This library brings functools.singledispatch to Python 2.6-3.3."""
 
     homepage = "https://pypi.python.org/pypi/singledispatch"
@@ -33,11 +33,9 @@ class PySingledispatch(Package):
 
     version('3.4.0.3', 'af2fc6a3d6cc5a02d0bf54d909785fcb')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-six')
-    depends_on('py-ordereddict', when="^python@:2.6.999")
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    # This dependency breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-ordereddict', when="^python@:2.6.999", type=('build', 'run'))  # noqa
diff --git a/var/spack/repos/builtin/packages/py-six/package.py b/var/spack/repos/builtin/packages/py-six/package.py
index 6298f39f32..7d653fd102 100644
--- a/var/spack/repos/builtin/packages/py-six/package.py
+++ b/var/spack/repos/builtin/packages/py-six/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySix(Package):
+class PySix(PythonPackage):
     """Python 2 and 3 compatibility utilities."""
 
     homepage = "https://pypi.python.org/pypi/six"
@@ -37,6 +37,3 @@ class PySix(Package):
     extends('python', ignore=r'bin/pytest')
 
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-sncosmo/package.py b/var/spack/repos/builtin/packages/py-sncosmo/package.py
index 6a1fb7c557..f9d2546da3 100644
--- a/var/spack/repos/builtin/packages/py-sncosmo/package.py
+++ b/var/spack/repos/builtin/packages/py-sncosmo/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySncosmo(Package):
+class PySncosmo(PythonPackage):
     """SNCosmo is a Python library for high-level supernova cosmology
     analysis."""
 
@@ -46,6 +46,3 @@ class PySncosmo(Package):
     depends_on('py-iminuit', type=('build', 'run'))
     depends_on('py-emcee', type=('build', 'run'))
     depends_on('py-nestle', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-snowballstemmer/package.py b/var/spack/repos/builtin/packages/py-snowballstemmer/package.py
index 44c0548932..cfeeeb26ce 100644
--- a/var/spack/repos/builtin/packages/py-snowballstemmer/package.py
+++ b/var/spack/repos/builtin/packages/py-snowballstemmer/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySnowballstemmer(Package):
+class PySnowballstemmer(PythonPackage):
     """This package provides 16 stemmer algorithms (15 + Poerter
     English stemmer) generated from Snowball algorithms."""
 
@@ -33,8 +33,3 @@ class PySnowballstemmer(Package):
     url      = "https://pypi.python.org/packages/source/s/snowballstemmer/snowballstemmer-1.2.1.tar.gz"
 
     version('1.2.1', '643b019667a708a922172e33a99bf2fa')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-sphinx-rtd-theme/package.py b/var/spack/repos/builtin/packages/py-sphinx-rtd-theme/package.py
index 9c57628e2c..4b9141d80c 100644
--- a/var/spack/repos/builtin/packages/py-sphinx-rtd-theme/package.py
+++ b/var/spack/repos/builtin/packages/py-sphinx-rtd-theme/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySphinxRtdTheme(Package):
+class PySphinxRtdTheme(PythonPackage):
     """ReadTheDocs.org theme for Sphinx."""
 
     homepage = "https://pypi.python.org/pypi/sphinx_rtd_theme"
@@ -34,9 +34,4 @@ class PySphinxRtdTheme(Package):
     version('0.1.10a0', '83bd95cae55aa8b773a8cc3a41094282',
             url="https://pypi.python.org/packages/da/6b/1b75f13d8aa3333f19c6cdf1f0bc9f52ea739cae464fbee050307c121857/sphinx_rtd_theme-0.1.10a0.tar.gz")
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-sphinx/package.py b/var/spack/repos/builtin/packages/py-sphinx/package.py
index 48b19d4d7b..b71f2ed8c5 100644
--- a/var/spack/repos/builtin/packages/py-sphinx/package.py
+++ b/var/spack/repos/builtin/packages/py-sphinx/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySphinx(Package):
+class PySphinx(PythonPackage):
     """Sphinx Documentation Generator."""
     homepage = "http://sphinx-doc.org"
     url      = "https://pypi.python.org/packages/source/S/Sphinx/Sphinx-1.3.1.tar.gz"
@@ -49,6 +49,3 @@ class PySphinx(Package):
     depends_on('py-alabaster@0.7:',          type=('build', 'run'))
     depends_on('py-imagesize', when='@1.4:', type=('build', 'run'))
     depends_on('py-sphinx-rtd-theme@0.1:',   type=('build', 'run'))  # optional as of 1.4
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-sqlalchemy/package.py b/var/spack/repos/builtin/packages/py-sqlalchemy/package.py
index 73e144b00e..f8221058a0 100644
--- a/var/spack/repos/builtin/packages/py-sqlalchemy/package.py
+++ b/var/spack/repos/builtin/packages/py-sqlalchemy/package.py
@@ -25,15 +25,10 @@
 from spack import *
 
 
-class PySqlalchemy(Package):
+class PySqlalchemy(PythonPackage):
     """The Python SQL Toolkit and Object Relational Mapper"""
 
     homepage = 'http://www.sqlalchemy.org/'
     url      = "https://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-1.0.12.tar.gz"
 
     version('1.0.12', '6d19ef29883bbebdcac6613cf391cac4')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-storm/package.py b/var/spack/repos/builtin/packages/py-storm/package.py
index 74fb2add0d..a6c2900414 100644
--- a/var/spack/repos/builtin/packages/py-storm/package.py
+++ b/var/spack/repos/builtin/packages/py-storm/package.py
@@ -22,19 +22,14 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from spack import depends_on, extends, version
-from spack import Package
+from spack import *
 
 
-class PyStorm(Package):
+class PyStorm(PythonPackage):
     """Storm is an object-relational mapper (ORM) for Python"""
     homepage = "https://storm.canonical.com/"
     url      = "https://launchpad.net/storm/trunk/0.20/+download/storm-0.20.tar.gz"
 
     version('0.20', '8628503141f0f06c0749d607ac09b9c7')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-symengine/package.py b/var/spack/repos/builtin/packages/py-symengine/package.py
index 7f7cd84a77..0817d394f2 100644
--- a/var/spack/repos/builtin/packages/py-symengine/package.py
+++ b/var/spack/repos/builtin/packages/py-symengine/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySymengine(Package):
+class PySymengine(PythonPackage):
     """Python wrappers for SymEngine, a symbolic manipulation library."""
 
     homepage = "https://github.com/symengine/symengine.py"
@@ -35,13 +35,11 @@ class PySymengine(Package):
     version('develop', git='https://github.com/symengine/symengine.py.git')
 
     # Build dependencies
-    extends('python')
     depends_on('python@2.7:2.8,3.3:')
     depends_on('py-setuptools',     type='build')
     depends_on('py-cython@0.19.1:')
     depends_on('cmake@2.8.7:',      type='build')
     depends_on('symengine@0.2.0:')
 
-    def install(self, spec, prefix):
-        python('setup.py', 'install', '--prefix=%s --symengine-dir=%s' %
-               (prefix, spec['symengine'].prefix))
+    def build_args(self, spec, prefix):
+        return ['--symengine-dir={0}'.format(spec['symengine'].prefix)]
diff --git a/var/spack/repos/builtin/packages/py-sympy/package.py b/var/spack/repos/builtin/packages/py-sympy/package.py
index 7d1b016263..58c4167e23 100644
--- a/var/spack/repos/builtin/packages/py-sympy/package.py
+++ b/var/spack/repos/builtin/packages/py-sympy/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PySympy(Package):
+class PySympy(PythonPackage):
     """SymPy is a Python library for symbolic mathematics."""
     homepage = "https://pypi.python.org/pypi/sympy"
     url      = "https://pypi.python.org/packages/source/s/sympy/sympy-0.7.6.tar.gz"
@@ -33,8 +33,4 @@ class PySympy(Package):
     version('0.7.6', '3d04753974306d8a13830008e17babca')
     version('1.0', '43e797de799f00f9e8fd2307dba9fab1')
 
-    extends('python')
     depends_on('py-mpmath', when='@1.0:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-tappy/package.py b/var/spack/repos/builtin/packages/py-tappy/package.py
index c195d08fd9..22bc15392f 100644
--- a/var/spack/repos/builtin/packages/py-tappy/package.py
+++ b/var/spack/repos/builtin/packages/py-tappy/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyTappy(Package):
+class PyTappy(PythonPackage):
     """Python TAP interface module for unit tests"""
     homepage = "https://github.com/mblayman/tappy"
     # base https://pypi.python.org/pypi/cffi
@@ -33,8 +33,9 @@ class PyTappy(Package):
 
     version('1.6', 'c8bdb93ad66e05f939905172a301bedf')
 
-    extends('python')
-    depends_on('py-setuptools', type='build')
+    extends('python', ignore='bin/nosetests|bin/pygmentize')
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
+    depends_on('python@2.6:2.7,3.2:3.4')
+    depends_on('py-nose', type=('build', 'run'))
+    depends_on('py-pygments', type=('build', 'run'))
+    depends_on('py-setuptools', type='build')
diff --git a/var/spack/repos/builtin/packages/py-terminado/package.py b/var/spack/repos/builtin/packages/py-terminado/package.py
index e9db560cd5..4cebe14fca 100644
--- a/var/spack/repos/builtin/packages/py-terminado/package.py
+++ b/var/spack/repos/builtin/packages/py-terminado/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyTerminado(Package):
+class PyTerminado(PythonPackage):
     """Terminals served to term.js using Tornado websockets"""
 
     homepage = "https://pypi.python.org/pypi/terminado"
@@ -33,11 +33,6 @@ class PyTerminado(Package):
 
     version('0.6', '5b6c65da27fe1ed07a9f80f0588cdaba')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-    depends_on('py-tornado@4:')
-    depends_on('py-ptyprocess')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-tornado@4:', type=('build', 'run'))
+    depends_on('py-ptyprocess', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-tornado/package.py b/var/spack/repos/builtin/packages/py-tornado/package.py
index 0de77a2d57..eb9c660947 100644
--- a/var/spack/repos/builtin/packages/py-tornado/package.py
+++ b/var/spack/repos/builtin/packages/py-tornado/package.py
@@ -25,25 +25,24 @@
 from spack import *
 
 
-class PyTornado(Package):
+class PyTornado(PythonPackage):
     """Tornado is a Python web framework and asynchronous networking
     library."""
     homepage = "https://github.com/tornadoweb/tornado"
-    # base https://pypi.python.org/pypi/tornado/
     url      = "https://github.com/tornadoweb/tornado/archive/v4.4.0.tar.gz"
 
     version('4.4.0', 'c28675e944f364ee96dda3a8d2527a87ed28cfa3')
-    
-    extends('python')
-    
+
     depends_on('py-setuptools', type='build')
-    
+
     # requirements from setup.py
-    depends_on('py-backports-ssl-match-hostname', when='^python@:2.7.8')
-    depends_on('py-singledispatch', when='^python@:3.3')
-    depends_on('py-certifi', when='^python@:3.3')
-    depends_on('py-backports-abc@0.4:', when='^python@:3.4')
-    
-    def install(self, spec, prefix):
-        setup_py('build')
-        setup_py('install', '--prefix={0}'.format(prefix))
+    # These dependencies breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-backports-ssl-match-hostname', when='^python@:2.7.8', type=('build', 'run'))  # noqa
+    # depends_on('py-singledispatch', when='^python@:3.3', type=('build', 'run'))  # noqa
+    # depends_on('py-certifi', when='^python@:3.3', type=('build', 'run'))
+    # depends_on('py-backports-abc@0.4:', when='^python@:3.4', type=('build', 'run'))  # noqa
+    depends_on('py-backports-ssl-match-hostname', type=('build', 'run'))
+    depends_on('py-singledispatch', type=('build', 'run'))
+    depends_on('py-certifi', type=('build', 'run'))
+    depends_on('py-backports-abc@0.4:', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-traitlets/package.py b/var/spack/repos/builtin/packages/py-traitlets/package.py
index 2a087dffb9..debd1dca43 100644
--- a/var/spack/repos/builtin/packages/py-traitlets/package.py
+++ b/var/spack/repos/builtin/packages/py-traitlets/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyTraitlets(Package):
+class PyTraitlets(PythonPackage):
     """Traitlets Python config system"""
 
     homepage = "https://pypi.python.org/pypi/traitlets"
@@ -40,12 +40,11 @@ class PyTraitlets(Package):
     version('4.0.0', 'b5b95ea5941fd9619b4704dfd8201568')
     version('4.0',   '14544e25ccf8e920ed1cbf833852481f')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('py-decorator', type=('build', 'run'))
-    depends_on('py-ipython-genutils')
-    depends_on('py-enum34', when='^python@:3.3')
+    depends_on('py-ipython-genutils', type=('build', 'run'))
 
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    # This dependency breaks concretization
+    # See https://github.com/LLNL/spack/issues/2793
+    # depends_on('py-enum34', when='^python@:3.3', type=('build', 'run'))
+    depends_on('py-enum34', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-tuiview/package.py b/var/spack/repos/builtin/packages/py-tuiview/package.py
index 5ce2043fb3..93726cf004 100644
--- a/var/spack/repos/builtin/packages/py-tuiview/package.py
+++ b/var/spack/repos/builtin/packages/py-tuiview/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyTuiview(Package):
+class PyTuiview(PythonPackage):
     """TuiView is a lightweight raster GIS with powerful raster attribute
     table manipulation abilities.
     """
@@ -35,10 +35,6 @@ class PyTuiview(Package):
 
     version('1.1.7', '4b3b38a820cc239c8ab4a181ac5d4c30')
 
-    extends("python")
     depends_on("py-pyqt", type=('build', 'run'))
     depends_on("py-numpy", type=('build', 'run'))
     depends_on("gdal")
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-twisted/package.py b/var/spack/repos/builtin/packages/py-twisted/package.py
index 80023c2123..e558adbc7f 100644
--- a/var/spack/repos/builtin/packages/py-twisted/package.py
+++ b/var/spack/repos/builtin/packages/py-twisted/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyTwisted(Package):
+class PyTwisted(PythonPackage):
     """An asynchronous networking framework written in Python"""
     homepage = "https://twistedmatrix.com/"
     url      = "https://pypi.python.org/packages/source/T/Twisted/Twisted-15.3.0.tar.bz2"
@@ -34,8 +34,3 @@ class PyTwisted(Package):
     version('15.3.0', 'b58e83da2f00b3352afad74d0c5c4599')
 
     depends_on('py-setuptools', type='build')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-unittest2/package.py b/var/spack/repos/builtin/packages/py-unittest2/package.py
index ddd50a37d4..d6a68c3535 100644
--- a/var/spack/repos/builtin/packages/py-unittest2/package.py
+++ b/var/spack/repos/builtin/packages/py-unittest2/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyUnittest2(Package):
+class PyUnittest2(PythonPackage):
     """unittest2 is a backport of the new features added to the unittest
     testing framework in Python 2.7 and onwards."""
 
@@ -34,8 +34,4 @@ class PyUnittest2(Package):
 
     version('1.1.0', 'f72dae5d44f091df36b6b513305ea000')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-unittest2py3k/package.py b/var/spack/repos/builtin/packages/py-unittest2py3k/package.py
index 4aee545d74..03134acfcd 100644
--- a/var/spack/repos/builtin/packages/py-unittest2py3k/package.py
+++ b/var/spack/repos/builtin/packages/py-unittest2py3k/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyUnittest2py3k(Package):
+class PyUnittest2py3k(PythonPackage):
     """unittest2 is a backport of the new features added to the unittest
     testing framework in Python 2.7 and 3.2. This is a Python 3 compatible
     version of unittest2."""
@@ -35,8 +35,5 @@ class PyUnittest2py3k(Package):
 
     version('0.5.1', '8824ff92044310d9365f90d892bf0f09')
 
-    extends('python')
+    depends_on('python@3:')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-urwid/package.py b/var/spack/repos/builtin/packages/py-urwid/package.py
index 61dec3f1cd..8e33d2bef2 100644
--- a/var/spack/repos/builtin/packages/py-urwid/package.py
+++ b/var/spack/repos/builtin/packages/py-urwid/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyUrwid(Package):
+class PyUrwid(PythonPackage):
     """A full-featured console UI library"""
     homepage = "http://urwid.org/"
     url      = "https://pypi.python.org/packages/source/u/urwid/urwid-1.3.0.tar.gz"
@@ -33,8 +33,3 @@ class PyUrwid(Package):
     version('1.3.0', 'a989acd54f4ff1a554add464803a9175')
 
     depends_on('py-setuptools', type='build')
-
-    extends("python")
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-vcversioner/package.py b/var/spack/repos/builtin/packages/py-vcversioner/package.py
index 246a3b7b43..81e4f7bdda 100644
--- a/var/spack/repos/builtin/packages/py-vcversioner/package.py
+++ b/var/spack/repos/builtin/packages/py-vcversioner/package.py
@@ -25,17 +25,12 @@
 from spack import *
 
 
-class PyVcversioner(Package):
+class PyVcversioner(PythonPackage):
     """Vcversioner: Take version numbers from version control."""
 
     homepage = "https://github.com/habnabit/vcversioner"
-    # base https://pypi.python.org/pypi/vcversioner/
     url      = "https://pypi.python.org/packages/source/v/vcversioner/vcversioner-2.16.0.0.tar.gz"
 
     version('2.16.0.0', 'aab6ef5e0cf8614a1b1140ed5b7f107d')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-virtualenv/package.py b/var/spack/repos/builtin/packages/py-virtualenv/package.py
index f373067144..5e6431b637 100644
--- a/var/spack/repos/builtin/packages/py-virtualenv/package.py
+++ b/var/spack/repos/builtin/packages/py-virtualenv/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyVirtualenv(Package):
+class PyVirtualenv(PythonPackage):
     """virtualenv is a tool to create isolated Python environments."""
     homepage = "http://virtualenv.readthedocs.org/projects/virtualenv/"
     url      = "https://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.11.6.tar.gz"
@@ -34,8 +34,4 @@ class PyVirtualenv(Package):
     version('13.0.1', '1ffc011bde6667f0e37ecd976f4934db')
     version('15.0.1', '28d76a0d9cbd5dc42046dd14e76a6ecc')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-wcsaxes/package.py b/var/spack/repos/builtin/packages/py-wcsaxes/package.py
index f7b5c13a13..be1d151ee9 100644
--- a/var/spack/repos/builtin/packages/py-wcsaxes/package.py
+++ b/var/spack/repos/builtin/packages/py-wcsaxes/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyWcsaxes(Package):
+class PyWcsaxes(PythonPackage):
     """WCSAxes is a framework for making plots of Astronomical data
     in Matplotlib."""
 
@@ -34,10 +34,7 @@ class PyWcsaxes(Package):
 
     version('0.8', 'de1c60fdae4c330bf5ddb9f1ab5ab920')
 
-    extends('python', ignore=r'bin/pbr')
+    extends('python', ignore=r'bin/')
     depends_on('py-numpy', type=('build', 'run'))
     depends_on('py-matplotlib', type=('build', 'run'))
     depends_on('py-astropy', type=('build', 'run'))
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-wcwidth/package.py b/var/spack/repos/builtin/packages/py-wcwidth/package.py
index 2740d749b5..c4846e2ee7 100644
--- a/var/spack/repos/builtin/packages/py-wcwidth/package.py
+++ b/var/spack/repos/builtin/packages/py-wcwidth/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyWcwidth(Package):
+class PyWcwidth(PythonPackage):
     """Measures number of Terminal column cells of wide-character codes"""
 
     homepage = "https://pypi.python.org/pypi/wcwidth"
@@ -33,9 +33,4 @@ class PyWcwidth(Package):
 
     version('0.1.7', 'b3b6a0a08f0c8a34d1de8cf44150a4ad')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/py-wheel/package.py b/var/spack/repos/builtin/packages/py-wheel/package.py
index ce495bf6d0..7e678df2e8 100644
--- a/var/spack/repos/builtin/packages/py-wheel/package.py
+++ b/var/spack/repos/builtin/packages/py-wheel/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyWheel(Package):
+class PyWheel(PythonPackage):
     """A built-package format for Python."""
 
     homepage = "https://pypi.python.org/pypi/wheel"
@@ -33,8 +33,4 @@ class PyWheel(Package):
 
     version('0.26.0', '4cfc6e7e3dc7377d0164914623922a10')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-widgetsnbextension/package.py b/var/spack/repos/builtin/packages/py-widgetsnbextension/package.py
index c0833806f8..916263bda6 100644
--- a/var/spack/repos/builtin/packages/py-widgetsnbextension/package.py
+++ b/var/spack/repos/builtin/packages/py-widgetsnbextension/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyWidgetsnbextension(Package):
+class PyWidgetsnbextension(PythonPackage):
     """IPython HTML widgets for Jupyter"""
 
     homepage = "https://pypi.python.org/pypi/widgetsnbextension"
@@ -33,11 +33,6 @@ class PyWidgetsnbextension(Package):
 
     version('1.2.6', '0aa4e152c9ba2d704389dc2453f448c7')
 
-    extends('python')
-
     depends_on('py-setuptools', type='build')
     depends_on('python@2.7:2.7.999,3.3:')
-    depends_on('py-jupyter-notebook@4.2.0:')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
+    depends_on('py-jupyter-notebook@4.2.0:', type=('build', 'run'))
diff --git a/var/spack/repos/builtin/packages/py-xlrd/package.py b/var/spack/repos/builtin/packages/py-xlrd/package.py
index 9638b8a36b..bbd2f57b07 100644
--- a/var/spack/repos/builtin/packages/py-xlrd/package.py
+++ b/var/spack/repos/builtin/packages/py-xlrd/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyXlrd(Package):
+class PyXlrd(PythonPackage):
     """Library for developers to extract data from Microsoft Excel (tm)
     spreadsheet files"""
 
@@ -33,8 +33,3 @@ class PyXlrd(Package):
     url      = "https://pypi.python.org/packages/source/x/xlrd/xlrd-0.9.4.tar.gz"
 
     version('0.9.4', '911839f534d29fe04525ef8cd88fe865')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-yapf/package.py b/var/spack/repos/builtin/packages/py-yapf/package.py
index eab4d54abc..5f5d32e3d0 100644
--- a/var/spack/repos/builtin/packages/py-yapf/package.py
+++ b/var/spack/repos/builtin/packages/py-yapf/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class PyYapf(Package):
+class PyYapf(PythonPackage):
     """ Yet Another Python Formatter """
     homepage = "https://github.com/google/yapf"
     # base https://pypi.python.org/pypi/cffi
@@ -33,8 +33,4 @@ class PyYapf(Package):
 
     version('0.2.1', '348ccf86cf2057872e4451b204fb914c')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/py-yt/package.py b/var/spack/repos/builtin/packages/py-yt/package.py
index cf35912191..6ab967d8a5 100644
--- a/var/spack/repos/builtin/packages/py-yt/package.py
+++ b/var/spack/repos/builtin/packages/py-yt/package.py
@@ -26,7 +26,7 @@
 from spack import *
 
 
-class PyYt(Package):
+class PyYt(PythonPackage):
     """Volumetric Data Analysis
 
        yt is a python package for analyzing and visualizing
@@ -54,8 +54,6 @@ class PyYt(Package):
     variant("h5py", default=True, description="enable h5py support")
     variant("scipy", default=True, description="enable scipy support")
 
-    extends("python")
-
     depends_on("py-astropy", type=('build', 'run'), when="+astropy")
     depends_on("py-cython", type=('build', 'run'))
     depends_on("py-h5py", type=('build', 'run'), when="+h5py")
@@ -67,12 +65,9 @@ class PyYt(Package):
     depends_on("py-sympy", type=('build', 'run'))
     depends_on("python @2.7:2.999,3.4:")
 
-    def install(self, spec, prefix):
-        setup_py("install", "--prefix=%s" % prefix)
-        self.check_install(spec, prefix)
-
-    def check_install(self, spec, prefix):
+    @PythonPackage.sanity_check('install')
+    def check_install(self):
         # The Python interpreter path can be too long for this
         # yt = Executable(join_path(prefix.bin, "yt"))
         # yt("--help")
-        python(join_path(prefix.bin, "yt"), "--help")
+        python(join_path(self.prefix.bin, "yt"), "--help")
diff --git a/var/spack/repos/builtin/packages/py-zmq/package.py b/var/spack/repos/builtin/packages/py-zmq/package.py
index ff2373c2f4..cbc0e02e6e 100644
--- a/var/spack/repos/builtin/packages/py-zmq/package.py
+++ b/var/spack/repos/builtin/packages/py-zmq/package.py
@@ -25,21 +25,16 @@
 from spack import *
 
 
-class PyZmq(Package):
+class PyZmq(PythonPackage):
     """PyZMQ: Python bindings for zeromq."""
     homepage = "https://github.com/zeromq/pyzmq"
-    # base https://pypi.python.org/pypi/pyzmq/
     url      = "https://github.com/zeromq/pyzmq/archive/v14.7.0.tar.gz"
 
     version('16.0.2', '4cf14a2995742253b2b009541f4436f4')
     version('14.7.0', 'bf304fb73d72aee314ff82d3554328c179938ecf')
 
-    extends('python')
     depends_on('py-setuptools', type='build')
-    depends_on('py-cython@0.16:')
-    depends_on('py-py')
-    depends_on('py-cffi')
+    depends_on('py-cython@0.16:', type=('build', 'run'))
+    depends_on('py-py', type=('build', 'run'))
+    depends_on('py-cffi', type=('build', 'run'))
     depends_on('zeromq')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix={0}'.format(prefix))
diff --git a/var/spack/repos/builtin/packages/python/package.py b/var/spack/repos/builtin/packages/python/package.py
index 7a5904ddd9..348b075e2f 100644
--- a/var/spack/repos/builtin/packages/python/package.py
+++ b/var/spack/repos/builtin/packages/python/package.py
@@ -99,6 +99,11 @@ def patch(self):
             r'\1setup.py\2 --no-user-cfg \3\6'
         )
 
+    @when('@:2.6,3.0:3.3')
+    def patch(self):
+        # See https://github.com/LLNL/spack/issues/1490
+        pass
+
     def install(self, spec, prefix):
         # TODO: The '--no-user-cfg' option for Python installation is only in
         # Python v2.7 and v3.4+ (see https://bugs.python.org/issue1180) and
diff --git a/var/spack/repos/builtin/packages/scons/package.py b/var/spack/repos/builtin/packages/scons/package.py
index d20e529384..54f894da6f 100644
--- a/var/spack/repos/builtin/packages/scons/package.py
+++ b/var/spack/repos/builtin/packages/scons/package.py
@@ -25,14 +25,9 @@
 from spack import *
 
 
-class Scons(Package):
+class Scons(PythonPackage):
     """SCons is a software construction tool"""
     homepage = "http://scons.org"
     url      = "http://downloads.sourceforge.net/project/scons/scons/2.5.0/scons-2.5.0.tar.gz"
 
     version('2.5.0', '9e00fa0df8f5ca5c5f5975b40e0ed354')
-
-    extends('python')
-
-    def install(self, spec, prefix):
-        setup_py('install', '--prefix=%s' % prefix)
diff --git a/var/spack/repos/builtin/packages/udunits2/package.py b/var/spack/repos/builtin/packages/udunits2/package.py
index 57d18e1ea4..cfc8e30c41 100644
--- a/var/spack/repos/builtin/packages/udunits2/package.py
+++ b/var/spack/repos/builtin/packages/udunits2/package.py
@@ -29,9 +29,9 @@ class Udunits2(AutotoolsPackage):
     """Automated units conversion"""
 
     homepage = "http://www.unidata.ucar.edu/software/udunits"
-    url      = "ftp://ftp.unidata.ucar.edu/pub/udunits/udunits-2.2.20.tar.gz"
+    url      = "ftp://ftp.unidata.ucar.edu/pub/udunits/udunits-2.2.21.tar.gz"
 
-    version('2.2.20', '1586b70a49dfe05da5fcc29ef239dce0')
+    version('2.2.21', '1f6d3375efc1f124790a4efb7102cdb7')
 
     depends_on('expat')
 
-- 
GitLab