Commit 0254a2fd authored by Carsten Klein's avatar Carsten Klein Committed by Paul Ganssle

Rename find_namepaces_ns to find_namespace_packages (#1423)

* fix #1419 PEP420: add find_namespace: directive

* fix #1419 PEP420: add find_namespace: directive to documentation

* fix #1419 PEP420: add tests

* fix #1419 PEP420: clean up code

* fix #1419 PEP420: fix typo in documentation

* fix #1419 PEP420: fix typo in documentation

* fix #1419 PEP420: clean up code

* fix #1419 PEP420: add changelog entry

* fixup! fix #1419 PEP420: add tests

* fix #1419 PEP420: cleanup code refactor markers

* #1420: Rename find_namespace_ns to find_namespace_packages

*  #1420: update changelog entry
parent bbf99b7e
Introduce find_packages_ns() to find PEP 420 namespace packages.
Introduce find_namespace_packages() to find PEP 420 namespace packages.
Add find_namespace: directive to config parser
......@@ -57,7 +57,7 @@ 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
* Full support for PEP 420 via ``find_namespace_packages()``, which is also backwards
compatible to the existing ``find_packages()`` for Python >= 3.3.
.. contents:: **Table of Contents**
......@@ -462,18 +462,18 @@ 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
``find_namespace_packages()``
-----------------------------
In Python 3.3+, ``setuptools`` also provides the ``find_namespace_packages`` 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``::
packages. Here is a minimal setup script using ``find_namespace_packages``::
from setuptools import setup, find_packages_ns
from setuptools import setup, find_namespace_packages
setup(
name="HelloWorld",
version="0.1",
packages=find_packages_ns(),
packages=find_namespace_packages(),
)
......@@ -490,16 +490,16 @@ namespace package is quite lenient, so for a project organized like so::
└── tests
└── test_mod1.py
A naive ``find_packages_ns()`` would install both ``namespace.mypackage`` and a
A naive ``find_namespace_packages()`` 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
from setuptools import setup, find_namespace_packages
setup(
name="namespace.mypackage",
version="0.1",
packages=find_packages_ns(include=['namespace.*'])
packages=find_namespace_packages(include=['namespace.*'])
)
Another option is to use the "src" layout, where all package code is placed in
......@@ -520,7 +520,7 @@ 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'))
packages=find_namespace_packages(where='src'))
.. _PEP 420: https://www.python.org/dev/peps/pep-0420/
......@@ -2389,8 +2389,8 @@ Metadata and options are set in the config sections of the same name.
* In some cases, complex values can be provided in dedicated subsections for
clarity.
* Some keys allow ``file:``, ``attr:``, and ``find:`` directives in order to
cover common usecases.
* Some keys allow ``file:``, ``attr:``, and ``find:`` and ``find_namespace:`` directives in
order to cover common usecases.
* Unknown keys are ignored.
......@@ -2479,7 +2479,7 @@ eager_resources list-comma
dependency_links list-comma
tests_require list-semi
include_package_data bool
packages find:, list-comma
packages find:, find_namespace:, list-comma
package_dir dict
package_data section
exclude_package_data section
......@@ -2489,10 +2489,13 @@ py_modules list-comma
.. note::
**packages** - The ``find:`` directive can be further configured
**packages** - The ``find:`` and ``find_namespace:`` directive can be further configured
in a dedicated subsection ``options.packages.find``. This subsection
accepts the same keys as the `setuptools.find` function:
accepts the same keys as the `setuptools.find_packages` and the
`setuptools.find_namespace_packages` function:
``where``, ``include``, and ``exclude``.
**find_namespace directive** - The ``find_namespace:`` directive is supported since Python >=3.3.
Configuration API
......
......@@ -26,7 +26,7 @@ __all__ = [
]
if PY3:
__all__.append('find_packages_ns')
__all__.append('find_namespace_packages')
__version__ = setuptools.version.__version__
......@@ -118,7 +118,7 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find
if PY3:
find_packages_ns = PEP420PackageFinder.find
find_namespace_packages = PEP420PackageFinder.find
def _install_setup_requires(attrs):
......
......@@ -8,7 +8,7 @@ from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
from setuptools.extern.packaging.version import LegacyVersion, parse
from setuptools.extern.six import string_types
from setuptools.extern.six import string_types, PY3
__metaclass__ = type
......@@ -515,16 +515,24 @@ class ConfigOptionsHandler(ConfigHandler):
:param value:
:rtype: list
"""
find_directive = 'find:'
find_directives = ['find:', 'find_namespace:']
trimmed_value = value.strip()
if not value.startswith(find_directive):
if not trimmed_value in find_directives:
return self._parse_list(value)
findns = trimmed_value == find_directives[1]
if findns and not PY3:
raise DistutilsOptionError('find_namespace: directive is unsupported on Python < 3.3')
# Read function arguments from a dedicated section.
find_kwargs = self.parse_section_packages__find(
self.sections.get('packages.find', {}))
from setuptools import find_packages
if findns:
from setuptools import find_namespace_packages as find_packages
else:
from setuptools import find_packages
return find_packages(**find_kwargs)
......
......@@ -2,5 +2,17 @@ import locale
import pytest
from setuptools.extern.six import PY2, PY3
__all__ = [
'fail_on_ascii', 'py2_only', 'py3_only'
]
is_ascii = locale.getpreferredencoding() == 'ANSI_X3.4-1968'
fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale")
py2_only = pytest.mark.skipif(not PY2, reason="Test runs on Python 2 only")
py3_only = pytest.mark.skipif(not PY3, reason="Test runs on Python 3 only")
......@@ -4,18 +4,20 @@ from distutils.errors import DistutilsOptionError, DistutilsFileError
from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
from . import py2_only, py3_only
class ErrConfigHandler(ConfigHandler):
"""Erroneous handler. Fails to implement required methods."""
def make_package_dir(name, base_dir):
def make_package_dir(name, base_dir, ns=False):
dir_package = base_dir
for dir_name in name.split('/'):
dir_package = dir_package.mkdir(dir_name)
init_file = dir_package.join('__init__.py')
init_file.write('')
init_file = None
if not ns:
init_file = dir_package.join('__init__.py')
init_file.write('')
return dir_package, init_file
......@@ -596,6 +598,60 @@ class TestOptions:
assert set(dist.packages) == set(
['fake_package', 'fake_package.sub_two'])
@py2_only
def test_find_namespace_directive_fails_on_py2(self, tmpdir):
dir_package, config = fake_env(
tmpdir,
'[options]\n'
'packages = find_namespace:\n'
)
with pytest.raises(DistutilsOptionError):
with get_dist(tmpdir) as dist:
dist.parse_config_files()
@py3_only
def test_find_namespace_directive(self, tmpdir):
dir_package, config = fake_env(
tmpdir,
'[options]\n'
'packages = find_namespace:\n'
)
dir_sub_one, _ = make_package_dir('sub_one', dir_package)
dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True)
with get_dist(tmpdir) as dist:
assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
}
config.write(
'[options]\n'
'packages = find_namespace:\n'
'\n'
'[options.packages.find]\n'
'where = .\n'
'include =\n'
' fake_package.sub_one\n'
' two\n'
)
with get_dist(tmpdir) as dist:
assert dist.packages == ['fake_package.sub_one']
config.write(
'[options]\n'
'packages = find_namespace:\n'
'\n'
'[options.packages.find]\n'
'exclude =\n'
' fake_package.sub_one\n'
)
with get_dist(tmpdir) as dist:
assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two'
}
def test_extras_require(self, tmpdir):
fake_env(
tmpdir,
......
......@@ -7,12 +7,12 @@ import platform
import pytest
from . import py3_only
from setuptools.extern.six import PY3
from setuptools import find_packages
py3_only = pytest.mark.xfail(not PY3, reason="Test runs on Python 3 only")
if PY3:
from setuptools import find_packages_ns
from setuptools import find_namespace_packages
# modeled after CPython's test.support.can_symlink
......@@ -156,26 +156,26 @@ class TestFindPackages:
@py3_only
def test_pep420_ns_package(self):
packages = find_packages_ns(
packages = find_namespace_packages(
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_packages_ns(
packages = find_namespace_packages(
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_packages_ns(self.dist_dir)
packages = find_namespace_packages(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_packages_ns(
packages = find_namespace_packages(
self.dist_dir, exclude=['docs', 'pkg.subpkg.assets'])
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
......@@ -183,6 +183,6 @@ class TestFindPackages:
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_packages_ns(self.dist_dir)
packages = find_namespace_packages(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