diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 901b8f115c552b8d905281451c31b9da523aec5a..1f8926e28d353a8b0d62c644c96341f482440b4b 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 0000000000000000000000000000000000000000..d21c291ae6c5456cfa64690a2574bf64602e3e51
--- /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 703705a32ca6a45d6052440fc5e94d5cfd66dfa5..6c0029794f97c117550f290ec7ee00ab5ff4c657 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 a488104282f8469b6f75cbaeb1b8e2dbac6b2179..257522958181f46d36b82546c9840a024de0933d 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 72656684e0404af46a3a4280fa273b327d425282..58eabb9e3bbab846af65a46a04d07fb161bbd1f0 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 d0b6857aafb4b473c4a27c20cf9fc4653e9e0ed4..80b95fcbfd3a06f22af88565e73307ab6d50854f 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 24cb5dce226014481cc93d4f9c5bb3466789f4c1..f2402c9bc634949ab2f5d804845f32adba1579e0 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 fe948f80612d394428af5710594a78256991164d..585540f23bf32287d1d69bf8cd337c1a132ceb7b 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 a42d936f054928e4e769dd0ffffc2eae901174e2..f275813d86c3ee97bda061f9c6ed311814ed16d9 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 193add18ef4bb67e04e3128100a12e29efc68be1..8688e0ee7ea57bd0b2c00c7da9d0625fb7e82278 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 629a3b515ee9a71fe15215712844d81593249af9..c892e2979ca382825bf4b93fcfaddc6728d3d48c 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 12d7ff84b6c604e092ecacc2b5fb9a4571969e44..844ceab34ea3b3246cf132a14c235c2ded3de5fb 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 d39cec83c894cadd716b512d8d54605fcf2a3ce8..7d062bff6aeb33a2cba68cfd63c13e2e684124dc 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 5158fa3ad4aed63ba3d4a926642f96b4e0de9af9..5950faa765aa2129f7a2975b4c86c4b6d137cc77 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 13b3a1abd4874382d62715c3c3473b460178f2ee..bf4679556b23222ea2ed84d03c12941cad54c196 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 d69051037b51e349299e87373980af6e65b29adb..4a35134e40ae634a3bda7c905321bc7af2644fea 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 5f8b6369856ef0a5ee4c797f20af9e26024c2fbe..3a90d02127d1bf928ab9a61dc46e763d36d1c0e0 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 83ede166d6caf558a2c36f76b9fe08045a8bbf40..3411e244f9c2a815ec9353b63ce45214734059e4 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 0000000000000000000000000000000000000000..4a6d7ed9d2f0c6fd2c3387371d13db1727ca7e18
--- /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 700c0ff4b304969eb5fc8896ba3478a3a9e5e4a4..b38f34b4123c9b8536b0ffa7a2320cdcd210bdba 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 d2d258105d08bcc59bcdcaa834b2c52457557b53..a1215ce39e0de20e4faad0c484b0e386b7e5a6a1 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 f06a8cbc371e415a956d8fe8ca0a57578f33eaf5..b59376d7b8bf41c123efc8871bcdf7c3d2cca295 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 c74a3f7c335b2380d42fdd01ef542f25f125755e..5eb8f414a869efbda4f0534cfc47a2b587c0e5a8 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 996a90df8eddddc8faf7c148eb5b6a31c62d36fa..959c0221edf01ed4d6789663e9ba9dbed450b80e 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 f2f76466876734dceda211d8389fc8f9eefd8952..c0fbae639be08ab31226b96f98ce36bec0050973 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 6885d6bd935b9c30e8600b8604b7aab54e007133..c9ba7ac15cc2018d5f5538385f3aa887347790a7 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 8cd8440b7e145deae2a1a4a5eb9849d7c1f7b94f..dc8fc08e24b5877400bd1aebc2755054660434dd 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 ceb220eef74c720b3c7e87fcc7f514ecd1fd698d..5bcda9f44913a0f21ad1c9a4e3db0ede2b4f4243 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 dff307cddda666063260cb2a0458842a78ec6bc7..f2b2a1501852d167793e9128eecff6de5ff4de23 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 55a4ef9ced34fa9f0bc9eabf79abee8140c67465..c84728cf3ea8afdab5819a0251639a838dec1596 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 c72046b6278ea4b65af3e610bd04066224469f44..4113c2ac0bb50bca54770ebb0dd87d58a87ce577 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 c9e77b3b086eb18aa25daa73c3573dffeb0f2202..3ab5ad029c41286300fd4eb43d0f9db7d7665a72 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 eff893cc82821276f8a8f8232fa176810736f47c..56403405e8ee412358f436b4128772b5da6f7328 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 30f764edc38802200aa4a9840f0d3a24bbe3c8b6..e5734866ec47f405405ff4261baf783f5a27b2d0 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 114131df9028acabee1208b8ea9a8dd928caf699..00741284df051fc32510839f2e925ea5a7e34420 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 d3627b32c55a32fb6352d5a248cf4305f3fff991..6419a9c40e6dbf0adcf451cf4346022f990da1c9 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 584572b47df70c8f067665d0765ae37c0aa67506..cc111ce09245bbbd431a05a8d5d4701b4b75b813 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 ed490cb39652b6093e21678e37e37028fc9e041c..e13d431f914dcf6866ad320097d3cf6283f97961 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 4515f68e0ab921ca59c7b8e671dee72f64c80c47..17d7cea3430b88704b9a03c74da6ddca1eb71bdf 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 b82a37cae986c50eb83c6b3d721ce80d96d3ab01..ea8b71f25f06132e126b745e74c6bfcde9112165 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 dc4e49d278f2a12589f66d237db33b3f3f254c71..f2fb0df555fe857b6204dc4d6fbba3e4b721f9b4 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 20bf4c7dae77ab907fdbc15469a1ae82cb44ed9c..c6c1d8134fa25222423c8f2d8c34f40609e13c68 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 f919a7e6c28cde4f47edbab44645631957c80dbb..2123f4eb3fc4edfd63caedc09a692ab443680434 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 e7eb7bebddd72306586f6190fdb3de2afca66c86..462dbfe802cb17543b5fd4afbdd0d7d7c8d9790d 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 f7b88d00fe6d2661b6cfff7cfb8dcc91792fce36..a23aa2585f1492899c46b0a3217a915d25cb25ae 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 dffd2463d3fc743a80cef67ea84a1b8c467a9f74..666905e5c0cf42d542d50891ee820af4129bc03d 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 0000000000000000000000000000000000000000..1757b4429791a7aa2a2da749e4751972e0126c6b
--- /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 941e64610ef2b5efb03a2beb4008ede9037c7532..a2d08f65027cc06f9be5acd1e8d103099b0b187c 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 3162b4131600ff9e65262474ddaa7cce573fe6db..0b93a0f2b899fb8ecccc11774b038d03ed1a41b1 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 d68cfa2ae4aa11d20c964b1c758568b56bd67b28..0303a8e43c1bc8cd04e40e7f17d6a9ba79b5eca3 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 75b20e08e9d327a7948143c1ad450375a0d8dce7..66e8a02130da10b9580a86076759270cbfdab776 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 b8ad5bf03a51f6a3aadaf8d88993b10ca29f4e05..277e090faa55cb493252c3ce91daf267c1cd6362 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 c9b2e9c168da210a85d3a9d71a2834c762f94dc1..eafee8e0848c7f6cac228498c10857c4b099abdd 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 60ee91c9b6410eaa3f488f9836e2b2125c54089e..964db1448f4a1303f53d19474f1f82c894cee44e 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 d9191952eea364aaac70e8e589f1d1eb102f4f2a..eafe8c252b54486b794f6b62bdb66943a8bb49b1 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 d41ee439dbbd8034acb5a85010f36d2ad485546f..b1a0ac6606cb91cd443e9932e6c43e0e3f3c3351 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 c8268c8aa2d2bc666bb81e02ffe8d295c78c9adb..b0c7b06362c11c7f815d66cd7d67ae19a7597ac7 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 1cc432e2e09198e80583727d90baf9e652454c3f..a5f3e53298beab5b47141088fee17d1e46dea4f3 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 ae4f1924370bdfefe0724b629c4d7df2052143a5..f650a91bb97724fba9af5714cbd6d10ca72e5002 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 c9eb51d2d4e2004e134dbae59780026b7c0d75ea..4c0d12b2458d6b426d61b39a6f43dc898a52f6d2 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 856276ec89e37651f224f4f3ce54cccb6892e95c..1e57e6a1d7d9fcef41553e379934a273641f90b2 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 e8c52493b5c005c10e34a86384e02698315dedca..4c20885760f625462ce54ff426f2c42ddb6c8fa8 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 09cb76c3d3d0a73ca7b7cfe04c174ed5b11d1087..42318faa2a06b7e93a6bf0e0751eef9db83685ed 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 799bc7b5d959d97dd2817edbc13fceaa92fa668d..0707d0b12fa300b8fb82c558899e08036b2c8817 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 cded451f3bb76a73e9ac564993bfb0170c14d1a2..23c81670213c0f51959fc5e7a619d119cb437f87 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 5ba5bfb9979d7fd04f89815540a52f2492e4bb4c..a31e3972de8f18eef54653155b8f791f0d9657f1 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 cac4da9e792fd9620a06154394c78a04fe5bf013..d808b0fc4bfb54356348094ba947e2bf0bfb73af 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 ec913acb16e60006fbf95198ff2753da83d4c07b..c413193cdc6c7bb9cdb936dec4d0203419c2339d 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 a0d57e7373c09c3b2d4a046008465ebe8e48e26e..0ebba77ac628c0eaafac3c290d9bb67e0c517a64 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 2daee1ed9a8f02761cd743e750a9c17bed84f459..cc859d4b787ab29fda32695e6a25812e828e0244 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 b33dfe73799dea02c27963acce64bae325bf71a7..21edbd1dc0b0e5bc0a408ec6eef542a029fe1604 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 6584aa248c41dcc357201186ac8f7b13788f6ed1..b02f954ccc9c0503630f300394a26378377a2f81 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 11b158439716f59ed66b7d4d5e9b9ca5343ecd18..7f8dc6b9860579c093726b6319dad0b86211bfff 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 846852aeb56b475cd27d0a223944e7375d15b6d9..d379e0bd03620b5a369338209344d8f1e2a9f6cf 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 e72b281665c86d5493d2433b667120daa99ca27b..9af74555b143ba4f305b5fa7e01c277e14aad0c3 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 14534a57cad392c5862dcfa172f622f9c2a6a258..8fd794aadb34d1df5ec7e311830b1d83b093be4a 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 41a1c8968692e41e1399c2e360a8d532c75d4224..0e221d355c3db16f5279f440b53a1222121496b6 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 f45236fc34a157cb049b34ced3f13e6a9ff50dcb..4ecf7f8fcf0740cf075ec6e3cdadc71505e115ef 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 a2ce2f514f3aa5c5f027ba9eee13061deb10201d..22dc9debe1eac0c06d3dd2c8607d56bf8d6825db 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 967b8e0613463e411b4248398f2862a6c1745f29..2d35320ca06c703e83e1c05e920eb38f3f9ba3fa 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 3086709a7935c6047778b32210e7cc63e8886995..6eca70c15ca9b859d35e3bf7f8a7e33f5fd5fc2b 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 f1872c85b474dcc433a0947c139270fea88a5935..050a018ffa0e924a111979de827fcf018112e992 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 b8e1ca5e0c9d1df26ac11ef7e15aad2a7b424f5a..ee89820f5b4003ead46c9241c24735f5b68f6c01 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 22b4d98b9df8860e731346bb9276e7b3499c1f05..25a5f04b8c67799724c4d9f9f8d259c4c45fdb3c 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 b9d86367421101d2bb16429d3120e77304b656fd..d396916035b3428f338dac9aa456983edc117d39 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 6e038b789cda5af9d86154e0d43407f121d0a3c8..b560990f00c0dc001d23159de9927070f27cfe42 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 eb2d948f221bfc8620bd0b2007868968ef908d07..c0da33054fbb92810fe5bc053c690b8224a2b3fa 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 8db18261b80d38139ddb7fe6b56c33c6cd1bc8bc..8951feff4c17dbb10d76e6093616c931f1a3f5e2 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 399f3c91ef8c4f30309b79062bee63599d29c34a..e5030abc7032335c05c0245540d09a77dd9859f9 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 0251e436b1199b7f1c5c8a643c77a7803a9829e9..2a320eb76d6de14026f20fd77eeef601972fee76 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 d4f732a5871f038c9c0e20663f9ff3f414f9c548..9ff15430183972b35e0c047dd219be8f06e9c05e 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 99b69fc30bb6d1676e46336f5c79ddab6c1a1170..5c194c44b63fda1d29dd8b8d5a00ed8c8f650223 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 748f0dd36b1af1bb745e30b9b1063a6b8f0f4f01..b7f1003e28200b5f88837201ba98b2e3364aaf53 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 09a810869dbe42452d281bc601635e852ec74d44..9bf9ff63fbc16560f5eac33c393b6a4b3ba73bff 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 f5d684962d43c8d690b0e898b8df6df21ac95780..fb14fb9b27267474ba4a3ab693a93a5462bde229 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 3125a822b471c52c26b62917d2d2e6acaf893b06..5729c5c3dd0a6313c9fcefc7db74ec7bcfb3d598 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 dca73f763f39c52267452ba9d6ecf5c346d3d1a7..73e1661245e134e5122ffead08dfea700d3d45e1 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 d249de64fadf01c7bd8d52a1fc6ca2b4700bc763..f5a1e537e253076beec8578b346c6ee237749bbe 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 5173864f62a9970921739637777fec4bd9262c7c..3293d94cd613f08342ca5524b0bb31f13d09be3d 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 55a323a0d686994ee609b4e8bf6b1a8203bee148..2203f68af082df0a477b8878433f176c468db1db 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 a839a1d0b62bac7174edc8272eedc418fcceaca0..949aab88c36236eaeeb6479946736b735fd6d3ea 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 5c81cf17010189322999298283f30023a9abf512..da48cb932f739cbf2d76a3c0c75f3b19cec3baca 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 d1186775bbf66dbcf08b231c1dda5587cbceebf9..4cdb3801a52299010f6f649d0d609078c43e0fa2 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 fc65e617bafa8b728b823830766abaa668153638..24197932273f8e946dd2fbfcfe865640c0fd6433 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 d9c2d3e0a46f5fd2f2766cac91019a91de7dff0a..66896bd83faf2f9102bcf7bf69e1755b1fe416f1 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 f624aaf3894331cb99a529109a36315e597aa125..5a963105bd2101bb50e7028a20b0e1e0a13593c6 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 efc36457454e671b640f7145ea291dcd7bd7de42..bb404c61f01bff562b80a30a3085b7f1ea8e4ca4 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 aed0859021b269bb36d504fc9e7c854c5805f21b..1db080ac97eae4975f4fb5546e9f0457fddbdb2b 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 b1f8aad33f130c1443f06e064bac262b4d79df1b..de09b380c9150b8c84dc75b73a3742ce0be7bb8c 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 9feb5eb8f6e440064c815e791828af7ba80e0c6e..3e668a2704a813ff24e9a68c68277d57b265c654 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 e7b91f44954e7fa8f2512f811d1d2489211352c7..8de5c39d32e321d60225af33e1f6930c0a420ce0 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 ab62440f8ead15527cba462a2f703afb140fa733..81a2a35064faa34d455d20063d964c560f141871 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 b2203ae04cf332fb3b73349d14a0df605fb5ae65..600a34cdfc3913927ded57e20ac20876311c01a9 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 96a5645541e04d77586ae7ea9d2019e73041c26f..d586f14f0d0082017ae3eda9c75d5cd643ae3a95 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 fc904d533455fb51f11e2d76128def08b956f38f..53674bb6e45355b40b75ee3ecac4b649cb16dbc2 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 c61b080e142693a05e46bc5b5e9fb79055e4db5d..42e3366cdffe9b71f98aeb883242017f6f750e27 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 675eb8f004dfd640638407a25d28b34f8f2e86f8..1dae1a128bdd926ef5f498439bdd9a9289cff370 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 c0c31a76862fd54b5f40c5f61ea7f848deaee2b5..84830c9bcf247d8f628c50ac098500aff3d21fb3 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 ffbc2323530445d9d3ca01f2d06784a81d86cf5c..f10b6d807f3f93c24e38b2f2c7545daab58aec0f 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 c3b4432d337451e8d6eda5d86906ad5f3e29b288..cb158a76d69a1c4842b693e065f9c48ed5a42e81 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 ab8c70140c27aab7a12475eec217472271fc096f..961aef78646ffa8670b90ce4d7df4128acd9d3ca 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 110d13f355d4b365dba8a0a8e511800543156633..3d9bfb2c2fcf1628ebf46374f53f1caa00c17e4e 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 1ad356ff4b4272400e2652ebe9600d00816b94f1..4af49306c2c5d7610fffa780eade9c4d427676cd 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 f8532c9175278b76f5253a4e95b585e2ff93ae62..9ed085f0310b534c9598855dd50d67c3826b94a5 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 486b86a4679cd78dd0a1e182c69f9c46a44a2fb8..7a905f9f989f13190343613cb26ab680b39babc7 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 d760fcaae93e65527faa98efe038489b0427a21d..94d8fdd0e6e5ebc940fc59476d7f9c80bb2ca7e8 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 0000000000000000000000000000000000000000..2abe4bac74e62453bc6208d973658b66e913aa47
--- /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 1aaf5d8f8a5d485865badbb828f0b166c67c0b7c..871016fdb196cce7f7fe5e6c6c7f97cf08937e1c 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 6308afd3dde24824922e2e376a5be21633f21b58..284a41894a2768714fa74f61e782bda0694a7092 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 dfb69f6983a3973cb96d4b23df0549b85527bb86..55f98ad19e692d0522a6e700bb3c12d4c5bed18c 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 48b56ef2faf9da8da6e76dca011b823400602aee..f0fe2e4e6c19c4a245e7ae97ab1aa5a335d4aecc 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 9ea21bdbdf6796ec5fc20a99a03bc13450945630..d05341f9ebe73d8abec63b2316149d6ea903e710 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 4a713e6565e5bcb3194c44178f63ab5d7adc6f8a..7c7102ce418a03eca6d9da92ece03e261aaf31f6 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 509a1f8b9431f55648f6ed1ee453c4fad270d1ea..85b6f631e134f64559f007f3c4357ed9081b3ff7 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 d696fdf776b09cd7158ea34d1c7fa09e7769e25d..d3558009589f17993f61027e620618d3bcede0d5 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 c6c23acd33e1f69cf0141584607eb76effa20e44..3ad51d5fc565f3850a3083ef78c064130a59057c 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 b2ee72822b905530c260d447c36fabaccc2484d8..3881f8bc885a35d44945ef1d25fc393e53e0a64f 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 05d8c16b44b7d643a17625a33e8574125f0a0136..9125ecb5f884dab9ad93f5acdc6c90c014474dc2 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 6298f39f32f2e1964bd5f69c3999cb12a1347c37..7d653fd102c4327b5354c3e49bce9a539585e4ea 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 6a1fb7c557ea3d3cf3e3657b8fe3a3622baab2dc..f9d2546da3fc0685b10632783896d8e2384a7ad6 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 44c054893213ee927820ae76913110f8edc316a4..cfeeeb26ce8824d3be67520c8159c0340cc3af7d 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 9c57628e2c2793be39520b5eb1f13eb67eb2d5b4..4b9141d80ce842dd9ab78bcd18c5af328fe333e4 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 48b19d4d7b71af71665a8310a29476e6d4f1e8da..b71f2ed8c56a6cd93bdd08c436248a2511965d66 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 73e144b00e3a7a9a3161759686fe5ba6b8dd24e8..f8221058a086c631b898832f6cf649c796b027fa 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 74fb2add0d64bce424e449898e84b296ed8b7a3f..a6c290041458528219b1f0006ef6581b27989bb7 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 7f7cd84a77861798d3177f9bdf9193b3151c5b8c..0817d394f222eddf221fc0ba3f3515c68dca49ba 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 7d1b016263c52ed205e0758b0f7e79df6f3d5220..58c4167e23ed06c477d33b940c2e0059fc6d639b 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 c195d08fd979d6b704f25be7c0c3026afe452fbf..22bc15392fc51694c60d76e138f1bfe49bccbddc 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 e9db560cd568b2f42b15a0a9dc4cd2207d350198..4cebe14fcab52a49ee7d78c08036f2abe4f88c72 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 0de77a2d5748d4b826aa1c94f620e09776a65334..eb9c660947321a3c08ff77a4dabbfb53bb245de2 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 2a087dffb98a7269e578ed2832bbd6ef9b64051c..debd1dca43a296c466823f49349cfa4dbc262760 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 5ce2043fb393963b5f7e93e3deb9c1c8681f1937..93726cf0047a86b196a1800f449be9c03375d190 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 80023c2123cdd5ab31dea88253e35e84f31bf1f8..e558adbc7f71e9a28af5bafbf7bc41e3444dadb9 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 ddd50a37d417958bb9710fbd4335a77ab5e35b81..d6a68c3535943a1bbb0f0fc2a513babd4ffc94fa 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 4aee545d7489e37588260a1a1015532dffef658f..03134acfcd35d546ec8e6cc13c6aa0d9f69c7cf2 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 61dec3f1cd20b123aaec6705da1873f6808c73c7..8e33d2bef2e16257d587a1cffe96e70239cd5229 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 246a3b7b439231537352b2665604219457bbcf7e..81e4f7bdda6bf4dccf6701287ec81097b98452e2 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 f37306714411c5f008586284ff35b792bce52839..5e6431b637283206fa5eb5223ff66cbc0de9f548 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 f7b5c13a133603843953b8053912ae5f311a80f5..be1d151ee9c907d8d8e8daf42093ed70288ecf5e 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 2740d749b59ca922d8fd3f0283610823c1797d4d..c4846e2ee75ff0ab3c09e8469f1b9bee0e6b86ca 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 ce495bf6d0e2bbf3cc517bccb410c393c3a9511d..7e678df2e86160e3dca607da2b6384cfda704ea6 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 c0833806f877464de7cdee4bdfe260743a42acc4..916263bda6d04f93608b96257ed9f2f12406e699 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 9638b8a36b73b06e2f873da4967fbe11de859654..bbd2f57b0702430ad542aae0458f12e0da5766ee 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 eab4d54abc7374d425025a59b90a9e279ce6e84d..5f5d32e3d00dff45e8605e36d81cd14550f1b019 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 cf359121916db1ee87b22ce6d69a328d346af2a3..6ab967d8a51b7495b420d38c055397e03e09b1f7 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 ff2373c2f4cf642b90b655c3f62796bcff346cef..cbc0e02e6e1a32bb0c9f47dd8bd21e2da8116c94 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 7a5904ddd92af5b076b4edaf65a7b31318cef9cf..348b075e2f4b0c9b45836b6386e6cf424b54e7bc 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 d20e529384575893e75e4e44d6091a5b480131f8..54f894da6fe4abda55b6c85681fa9660d6824c7b 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 57d18e1ea44d1c3086b12a7efc598801db91ae0b..cfc8e30c413d586764df9f4559663b6d2fda7c1a 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')