From d8f8de7f46979736e0e1aab3343d4f7a29e2e0c2 Mon Sep 17 00:00:00 2001 From: Carsten Klein Date: Wed, 4 Jul 2018 10:03:37 -0400 Subject: [PATCH 1/5] Add tests for find_packages_ns() --- setuptools/tests/test_find_packages.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index a6023de9d5..02ae5a946d 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']) + From 2b7a2dd7bfd7d742c8816d45150b9e495f5970f8 Mon Sep 17 00:00:00 2001 From: Carsten Klein Date: Wed, 4 Jul 2018 10:04:01 -0400 Subject: [PATCH 2/5] Add find_packages_ns() This fixes GH #97 by introducing an alternate version of find_packages that works with PEP 420 namespace packages. --- setuptools/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setuptools/__init__.py b/setuptools/__init__.py index ce55ec351d..e705f0d1af 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 @@ -111,6 +117,9 @@ def _looks_like_package(path): find_packages = PackageFinder.find +if PY3: + find_packages_ns = PEP420PackageFinder.find + def _install_setup_requires(attrs): # Note: do not use `setuptools.Distribution` directly, as From b0a89a1c00ded4fe98f2e161066bc7b1ff933fc4 Mon Sep 17 00:00:00 2001 From: Carsten Klein Date: Wed, 4 Jul 2018 10:04:49 -0400 Subject: [PATCH 3/5] Add documentation for find_packages_ns() --- docs/setuptools.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index c82dc51133..1d509a73c0 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 From 26fce7125849d090dd65dd7c046515a347357f01 Mon Sep 17 00:00:00 2001 From: Carsten Klein Date: Wed, 4 Jul 2018 10:06:25 -0400 Subject: [PATCH 4/5] Add changelog for PR #1312 --- changelog.d/1312.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1312.change.rst diff --git a/changelog.d/1312.change.rst b/changelog.d/1312.change.rst new file mode 100644 index 0000000000..9314f9e142 --- /dev/null +++ b/changelog.d/1312.change.rst @@ -0,0 +1 @@ +Introduce find_packages_ns() to find PEP 420 namespace packages. From a5797d2c468c1d7005ea36b77ce00941e7c8b251 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 4 Jul 2018 10:52:04 -0400 Subject: [PATCH 5/5] Expand documentation for find_packages_ns This moves the documentation out of "Basic Usage" and expands on some of the caveats of this approach. --- docs/setuptools.txt | 76 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 1d509a73c0..9a088254cc 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -110,21 +110,6 @@ 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 @@ -480,6 +465,67 @@ argument in your setup script. Especially since it frees you from having to remember to modify your setup script whenever your project grows additional top-level packages or subpackages. +``find_packages_ns()`` +---------------------- +In Python 3.3+, ``setuptools`` also provides the ``find_packages_ns`` variant +of ``find_packages``, which has the same function signature as +``find_packages``, but works with `PEP 420`_ compliant implicit namespace +packages. Here is a minimal setup script using ``find_packages_ns``:: + + from setuptools import setup, find_packages_ns + setup( + name="HelloWorld", + version="0.1", + packages=find_packages_ns(), + ) + + +Keep in mind that according to PEP 420, you may have to either re-organize your +codebase a bit or define a few exclusions, as the definition of an implicit +namespace package is quite lenient, so for a project organized like so:: + + + ├── namespace + │   └── mypackage + │   ├── __init__.py + │   └── mod1.py + ├── setup.py + └── tests + └── test_mod1.py + +A naive ``find_packages_ns()`` would install both ``namespace.mypackage`` and a +top-level package called ``tests``! One way to avoid this problem is to use the +``include`` keyword to whitelist the packages to include, like so:: + + from setuptools import setup, find_packages_ns + + setup( + name="namespace.mypackage", + version="0.1", + packages=find_packages_ns(include=['namespace.*']) + ) + +Another option is to use the "src" layout, where all package code is placed in +the ``src`` directory, like so:: + + + ├── setup.py + ├── src + │   └── namespace + │   └── mypackage + │   ├── __init__.py + │   └── mod1.py + └── tests + └── test_mod1.py + +With this layout, the package directory is specified as ``src``, as such:: + + setup(name="namespace.mypackage", + version="0.1", + package_dir={'': 'src'}, + packages=find_packages_ns(where='src')) + +.. _PEP 420: https://www.python.org/dev/peps/pep-0420/ Automatic Script Creation =========================