diff --git a/CHANGES.rst b/CHANGES.rst index d52ed48fca1..bf7386366df 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,7 @@ v39.2.0 ------- +* #97: PEP 420: introduce find_packages_ns() * #1359: Support using "file:" to load a PEP 440-compliant package version from a text file. * #1360: Fixed issue with a mismatch between the name of the package and the diff --git a/docs/setuptools.txt b/docs/setuptools.txt index c82dc51133f..1d509a73c0d 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -59,6 +59,9 @@ Feature Highlights: * Create extensible applications and frameworks that automatically discover extensions, using simple "entry points" declared in a project's setup script. +* Full support for PEP 420 via ``find_packages_ns()``, which is also backwards + compatible to the existing ``find_packages()`` for Python >= 3.3. + .. contents:: **Table of Contents** .. _ez_setup.py: `bootstrap module`_ @@ -107,6 +110,21 @@ As you can see, it doesn't take much to use setuptools in a project. Run that script in your project folder, alongside the Python packages you have developed. +For Python 3.3+, and whenever you are using PEP 420 compliant implicit +namespace packages, you can use ``find_packages_ns()`` instead. +But keep in mind that if you do, you might have to either define a few +exclusions or reorganize your codebase a little bit so that the new function +does not find for example your test fixtures and treat them as implicit +namespace packages. And here is a minimal setup script using +``find_packages_ns()``:: + + from setuptools import setup, find_packages_ns as find_packages + setup( + name="HelloWorld", + version="0.1", + packages=find_packages(), + ) + Invoke that script to produce eggs, upload to PyPI, and automatically include all packages in the directory where the setup.py lives. See the `Command Reference`_ section below to see what diff --git a/setuptools/__init__.py b/setuptools/__init__.py index ce55ec351d0..a1613445c9b 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,12 +1,14 @@ """Extensions to the 'distutils' for large or complex distributions""" import os +import sys import functools import distutils.core import distutils.filelist from distutils.util import convert_path from fnmatch import fnmatchcase +from setuptools.extern.six import PY3 from setuptools.extern.six.moves import filter, map import setuptools.version @@ -17,11 +19,15 @@ __metaclass__ = type + __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', - 'find_packages', + 'find_packages' ] +if PY3: + __all__.append('find_packages_ns') + __version__ = setuptools.version.__version__ bootstrap_install_from = None @@ -110,6 +116,7 @@ def _looks_like_package(path): find_packages = PackageFinder.find +find_packages_ns = PEP420PackageFinder.find def _install_setup_requires(attrs): diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index a6023de9d5a..02ae5a946dc 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -7,14 +7,15 @@ import pytest -import setuptools +from setuptools.extern.six import PY3 from setuptools import find_packages -find_420_packages = setuptools.PEP420PackageFinder.find +py3_only = pytest.mark.xfail(not PY3, reason="Test runs on Python 3 only") +if PY3: + from setuptools import find_packages_ns # modeled after CPython's test.support.can_symlink - def can_symlink(): TESTFN = tempfile.mktemp() symlink_path = TESTFN + "can_symlink" @@ -153,30 +154,35 @@ def test_symlinked_packages_are_included(self): def _assert_packages(self, actual, expected): assert set(actual) == set(expected) + @py3_only def test_pep420_ns_package(self): - packages = find_420_packages( + packages = find_packages_ns( self.dist_dir, include=['pkg*'], exclude=['pkg.subpkg.assets']) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_includes(self): - packages = find_420_packages( + packages = find_packages_ns( self.dist_dir, exclude=['pkg.subpkg.assets']) self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_includes_or_excludes(self): - packages = find_420_packages(self.dist_dir) - expected = [ - 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] + packages = find_packages_ns(self.dist_dir) + expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] self._assert_packages(packages, expected) + @py3_only def test_regular_package_with_nested_pep420_ns_packages(self): self._touch('__init__.py', self.pkg_dir) - packages = find_420_packages( + packages = find_packages_ns( self.dist_dir, exclude=['docs', 'pkg.subpkg.assets']) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) + @py3_only def test_pep420_ns_package_no_non_package_dirs(self): shutil.rmtree(self.docs_dir) shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) - packages = find_420_packages(self.dist_dir) + packages = find_packages_ns(self.dist_dir) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) +