Commit 89155abb authored by Paul Ganssle's avatar Paul Ganssle Committed by GitHub

Merge pull request #1312 from coldrye-collaboration/gh-97

fix #97 PEP420: find_packages()
parents e50d77ef a5797d2c
Introduce find_packages_ns() to find PEP 420 namespace packages.
......@@ -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
=========================
......
"""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
......
......@@ -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'])
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment