summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Ganssle <pganssle@users.noreply.github.com>2018-07-11 09:16:49 -0400
committerGitHub <noreply@github.com>2018-07-11 09:16:49 -0400
commit89155abb4222cf5a9dc81120e5c71e26b5af68f9 (patch)
tree8f0559e943fafedd6720d596ac8ebdecdcf8bcc6
parente50d77efb25d9db04a87ceea483d6b74fc58cd91 (diff)
parenta5797d2c468c1d7005ea36b77ce00941e7c8b251 (diff)
downloadpython-setuptools-git-89155abb4222cf5a9dc81120e5c71e26b5af68f9.tar.gz
Merge pull request #1312 from coldrye-collaboration/gh-97
fix #97 PEP420: find_packages()
-rw-r--r--changelog.d/1312.change.rst1
-rw-r--r--docs/setuptools.txt64
-rw-r--r--setuptools/__init__.py11
-rw-r--r--setuptools/tests/test_find_packages.py26
4 files changed, 91 insertions, 11 deletions
diff --git a/changelog.d/1312.change.rst b/changelog.d/1312.change.rst
new file mode 100644
index 00000000..9314f9e1
--- /dev/null
+++ b/changelog.d/1312.change.rst
@@ -0,0 +1 @@
+Introduce find_packages_ns() to find PEP 420 namespace packages.
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index 511a9898..0660e14d 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -57,6 +57,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`_
@@ -459,6 +462,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
=========================
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index ce55ec35..e705f0d1 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 @@ from . import monkey
__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 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find
+if PY3:
+ find_packages_ns = PEP420PackageFinder.find
+
def _install_setup_requires(attrs):
# Note: do not use `setuptools.Distribution` directly, as
diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py
index a6023de9..02ae5a94 100644
--- a/setuptools/tests/test_find_packages.py
+++ b/setuptools/tests/test_find_packages.py
@@ -7,14 +7,15 @@ import platform
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 @@ class TestFindPackages:
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'])
+