+v60.10.0
+--------
+
+
+Changes
+^^^^^^^
+* #2971: Deprecated upload_docs command, to be removed in the future.
+* #3137: Use samefile from stdlib, supported on Windows since Python 3.2.
+* #3170: Adopt nspektr (vendored) to implement Distribution._install_dependencies.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #3144: Added documentation on using console_scripts from setup.py, which was previously only shown in setup.cfg -- by :user:`xhlulu`
+* #3148: Added clarifications about ``MANIFEST.in``, that include links to PyPUG docs
+ and more prominent mentions to using a revision control system plugin as an
+ alternative.
+* #3148: Removed mention to ``pkg_resources`` as the recommended way of accessing data
+ files, in favour of :doc:`importlib.resources`.
+ Additionally more emphasis was put on the fact that *package data files* reside
+ **inside** the *package directory* (and therefore should be *read-only*).
+
+Misc
+^^^^
+* #3120: Added workaround for intermittent failures of backend tests on PyPy.
+ These tests now are marked with `XFAIL
+ <https://docs.pytest.org/en/stable/how-to/skipping.html>`_, instead of erroring
+ out directly.
+* #3124: Improved configuration for :pypi:`rst-linker` (extension used to build the
+ changelog).
+* #3133: Enhanced isolation of tests using virtual environments - PYTHONPATH is not leaking to spawned subprocesses -- by :user:`befeleme`
+* #3147: Added options to provide a pre-built ``setuptools`` wheel or sdist for being
+ used during tests with virtual environments.
+ Paths for these pre-built distribution files can now be set via the environment
+ variables: ``PRE_BUILT_SETUPTOOLS_SDIST`` and ``PRE_BUILT_SETUPTOOLS_WHEEL``.
+
+
v60.9.3
-------
Documentation changes
^^^^^^^^^^^^^^^^^^^^^
-* #2792: Document how the legacy and non-legacy versions are compared, and reference to the `PEP 440 <https://www.python.org/dev/peps/pep-0440/>`_ scheme.
+* #2792: Document how the legacy and non-legacy versions are compared, and reference to the PEP 440 scheme.
v58.1.0
Jython.
* Work around Jython #1980 and Jython #1981.
* Distribute #334: Provide workaround for packages that reference ``sys.__stdout__``
- such as numpy does. This change should address
- `virtualenv #359 <https://github.com/pypa/virtualenv/issues/359>`_ as long
+ such as numpy does. This change should address pypa/virtualenv#359 as long
as the system encoding is UTF-8 or the IO encoding is specified in the
environment, i.e.::
* BB Pull Request #14: Honor file permissions in zip files.
* Distribute #327: Merged pull request #24 to fix a dependency problem with pip.
-* Merged pull request #23 to fix https://github.com/pypa/virtualenv/issues/301.
+* Merged pull request #23 to fix pypa/virtualenv#301.
* If Sphinx is installed, the ``upload_docs`` command now runs ``build_sphinx``
to produce uploadable documentation.
* Distribute #326: ``upload_docs`` provided mangled auth credentials under Python 3.
Metadata-Version: 2.1
Name: setuptools
-Version: 60.9.3
+Version: 60.10.0
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: https://github.com/pypa/setuptools
Author: Python Packaging Authority
),
replace=[
dict(
- pattern=r'(Issue )?#(?P<issue>\d+)',
+ pattern=r'(?<!\w)(Issue )?#(?P<issue>\d+)',
url='{package_url}/issues/{issue}',
),
dict(
url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
),
dict(
- pattern=r'PEP[- ](?P<pep_number>\d+)',
+ pattern=r'(?<![`/\w])PEP[- ](?P<pep_number>\d+)',
url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
),
dict(
url='{GH}/jaraco/setuptools_svn/issues/{setuptools_svn}',
),
dict(
- pattern=r'pypa/distutils#(?P<distutils>\d+)',
- url='{GH}/pypa/distutils/issues/{distutils}',
+ pattern=r'pypa/(?P<issue_repo>[\-\.\w]+)#(?P<issue_number>\d+)',
+ url='{GH}/pypa/{issue_repo}/issues/{issue_number}',
),
dict(
- pattern=r'pypa/distutils@(?P<distutils_commit>[\da-f]+)',
- url='{GH}/pypa/distutils/commit/{distutils_commit}',
+ pattern=r'pypa/(?P<commit_repo>[\-\.\w]+)@(?P<commit_number>[\da-f]+)',
+ url='{GH}/pypa/{commit_repo}/commit/{commit_number}',
),
dict(
pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
]
intersphinx_mapping['pip'] = 'https://pip.pypa.io/en/latest', None
+intersphinx_mapping['PyPUG'] = ('https://packaging.python.org/en/latest/', None)
+intersphinx_mapping['packaging'] = ('https://packaging.pypa.io/en/latest/', None)
+intersphinx_mapping['importlib-resources'] = (
+ 'https://importlib-resources.readthedocs.io/en/latest', None
+)
Setuptools and the PyPA have a `stated goal <https://github.com/pypa/packaging-problems/issues/127>`_ to make Setuptools the reference API for distutils.
-Since the 49.1.2 release, Setuptools includes a local, vendored copy of distutils (from late copies of CPython) that is disabled by default. To enable the use of this copy of distutils when invoking setuptools, set the enviroment variable:
+Since the 60.0.0 release, Setuptools includes a local, vendored copy of distutils (from late copies of CPython) that is enabled by default. To disable the use of this copy of distutils when invoking setuptools, set the enviroment variable:
- SETUPTOOLS_USE_DISTUTILS=local
+ SETUPTOOLS_USE_DISTUTILS=stdlib
-This behavior is planned to become the default.
Prefer Setuptools
-----------------
``distutils.command.{build_clib,build_ext,build_py,sdist}`` → ``setuptools.command.*``
-``distutils.log`` → (no replacement yet)
+``distutils.log`` → :mod:`logging` (standard library)
-``distutils.version.*`` → ``packaging.version.*``
+``distutils.version.*`` → :doc:`packaging.version.* <packaging:version>`
``distutils.errors.*`` → ``setuptools.errors.*`` [#errors]_
+
+Migration advice is also provided by :pep:`PEP 632 <632#migration-advice>`.
+
If a project relies on uses of ``distutils`` that do not have a suitable replacement above, please search the `Setuptools issue tracker <https://github.com/pypa/setuptools/issues/>`_ and file a request, describing the use-case so that Setuptools' maintainers can investigate. Please provide enough detail to help the maintainers understand how distutils is used, what value it provides, and why that behavior should be supported.
individually in setup.py
* Automatically include all relevant files in your source distributions,
- without needing to create a ``MANIFEST.in`` file, and without having to force
- regeneration of the ``MANIFEST`` file when your source tree changes.
+ without needing to create a |MANIFEST.in|_ file, and without having to force
+ regeneration of the ``MANIFEST`` file when your source tree changes
+ [#manifest]_.
* Automatically generate wrapper scripts or Windows (console and GUI) .exe
files for any number of "main" functions in your project. (Note: this is not
.. _GitHub Discussions: https://github.com/pypa/setuptools/discussions
.. _setuptools bug tracker: https://github.com/pypa/setuptools/
+
+
+----
+
+
+.. [#manifest] The default behaviour for ``setuptools`` will work well for pure
+ Python packages, or packages with simple C extensions (that don't require
+ any special C header). See :ref:`Controlling files in the distribution` and
+ :doc:`userguide/datafiles` for more information about complex scenarios, if
+ you want to include other types of files.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
The distutils have traditionally allowed installation of "data files", which
are placed in a platform-specific location. However, the most common use case
for data files distributed with a package is for use *by* the package, usually
-by including the data files in the package directory.
+by including the data files **inside the package directory**.
-Setuptools offers three ways to specify data files to be included in your
-packages. First, you can simply use the ``include_package_data`` keyword,
-e.g.::
+Setuptools offers three ways to specify this most common type of data files to
+be included in your package's [#datafiles]_.
+First, you can simply use the ``include_package_data`` keyword, e.g.::
from setuptools import setup, find_packages
setup(
)
This tells setuptools to install any data files it finds in your packages.
-The data files must be specified via the distutils' ``MANIFEST.in`` file.
+The data files must be specified via the |MANIFEST.in|_ file.
(They can also be tracked by a revision control system, using an appropriate
-plugin. See the section below on :ref:`Adding Support for Revision
+plugin such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn`.
+See the section below on :ref:`Adding Support for Revision
Control Systems` for information on how to write such plugins.)
If you want finer-grained control over what files are included (for example,
``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if
the ``setup.py`` ``package_data`` list is updated before calling ``setup.py``.
-(Note: although the ``package_data`` argument was previously only available in
-``setuptools``, it was also added to the Python ``distutils`` package as of
-Python 2.4; there is `some documentation for the feature`__ available on the
-python.org website. If using the setuptools-specific ``include_package_data``
-argument, files specified by ``package_data`` will *not* be automatically
-added to the manifest unless they are listed in the MANIFEST.in file.)
+.. note::
+ If using the ``include_package_data`` argument, files specified by
+ ``package_data`` will *not* be automatically added to the manifest unless
+ they are listed in the |MANIFEST.in|_ file or by a plugin like
+ :pypi:`setuptools-scm` or :pypi:`setuptools-svn`.
-__ https://docs.python.org/3/distutils/setupscript.html#installing-package-data
+.. https://docs.python.org/3/distutils/setupscript.html#installing-package-data
Sometimes, the ``include_package_data`` or ``package_data`` options alone
aren't sufficient to precisely define what files you want included. For
In summary, the three options allow you to:
``include_package_data``
- Accept all data files and directories matched by ``MANIFEST.in``.
+ Accept all data files and directories matched by |MANIFEST.in|_ or added by
+ a :ref:`plugin <Adding Support for Revision Control Systems>`.
``package_data``
Specify additional patterns to match files that may or may
- not be matched by ``MANIFEST.in`` or found in source control.
+ not be matched by |MANIFEST.in|_ or added by
+ a :ref:`plugin <Adding Support for Revision Control Systems>`.
``exclude_package_data``
Specify patterns for data files and directories that should *not* be
order to find the location of data files. However, this manipulation isn't
compatible with PEP 302-based import hooks, including importing from zip files
and Python Eggs. It is strongly recommended that, if you are using data files,
-you should use the :ref:`ResourceManager API` of ``pkg_resources`` to access
-them. The ``pkg_resources`` module is distributed as part of setuptools, so if
-you're using setuptools to distribute your package, there is no reason not to
-use its resource management API. See also `Importlib Resources`_ for
-a quick example of converting code that uses ``__file__`` to use
-``pkg_resources`` instead.
+you should use :mod:`importlib.resources` to access them.
+:mod:`importlib.resources` was added to Python 3.7 and the latest version of
+the library is also available via the :pypi:`importlib-resources` backport.
+See :doc:`importlib-resources:using` for detailed instructions [#importlib]_.
+
+.. tip:: Files inside the package directory should be *read-only* to avoid a
+ series of common problems (e.g. when multiple users share a common Python
+ installation, when the package is loaded from a zip file, or when multiple
+ instances of a Python application run in parallel).
-.. _Importlib Resources: https://docs.python.org/3/library/importlib.html#module-importlib.resources
+ If your Python package needs to write to a file for shared data or configuration,
+ you can use standard platform/OS-specific system directories, such as
+ ``~/.local/config/$appname`` or ``/usr/share/$appname/$version`` (Linux specific) [#system-dirs]_.
+ A common approach is to add a read-only template file to the package
+ directory that is then copied to the correct system directory if no
+ pre-existing file is found.
Non-Package Data Files
no supported facility to reliably retrieve these resources.
Instead, the PyPA recommends that any data files you wish to be accessible at
-run time be included in the package.
+run time be included **inside the package**.
+
+
+----
+
+.. [#datafiles] ``setuptools`` consider a *package data file* any non-Python
+ file **inside the package directory** (i.e., that co-exists in the same
+ location as the regular ``.py`` files being distributed).
+
+.. [#system-dirs] These locations can be discovered with the help of
+ third-party libraries such as :pypi:`platformdirs`.
+
+.. [#importlib] Recent versions of :mod:`importlib.resources` available in
+ Pythons' standard library should be API compatible with
+ :pypi:`importlib-metadata`. However this might vary depending on which version
+ of Python is installed.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
``timmins.hello_world``, add a console script entry point to
``setup.cfg``:
-.. code-block:: ini
+.. tab:: setup.cfg
+
+ .. code-block:: ini
+
+ [options.entry_points]
+ console_scripts =
+ hello-world = timmins:hello_world
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ from setuptools import setup
+
+ setup(
+ name='timmins',
+ version='0.0.1',
+ packages=['timmins'],
+ # ...
+ entry_points={
+ 'console_scripts': [
+ 'hello-world=timmins:hello_world',
+ ]
+ }
+ )
- [options.entry_points]
- console_scripts =
- hello-world = timmins:hello_world
After installing the package, a user may invoke that function by simply calling
``hello-world`` on the command line.
then make an explicit declaration of ``True`` or ``False`` for the ``zip_safe``
flag, so that it will not be necessary for ``bdist_egg`` to try to guess
whether your project can work as a zipfile.
+
+
+.. _Controlling files in the distribution:
+
+Controlling files in the distribution
+-------------------------------------
+
+For the most common use cases, ``setuptools`` will automatically find out which
+files are necessary for distributing the package.
+This includes all :term:`pure Python modules <Pure Module>` in the
+``py_modules`` or ``packages`` configuration, and the C sources (but not C
+headers) listed as part of extensions when creating a :term:`Source
+Distribution (or "sdist")`.
+
+However, when building more complex packages (e.g. packages that include
+non-Python files, or that need to use custom C headers), you might find that
+not all files present in your project folder are included in package
+:term:`distribution archive <Distribution Package>`.
+
+In these situations you can use a ``setuptools``
+:ref:`plugin <Adding Support for Revision Control Systems>`,
+such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn` to automatically
+include all files tracked by your Revision Control System into the ``sdist``.
+
+.. _Using MANIFEST.in:
+
+Alternatively, if you need finer control, you can add a ``MANIFEST.in`` file at
+the root of your project.
+This file contains instructions that tell ``setuptools`` which files exactly
+should be part of the ``sdist`` (or not).
+A comprehensive guide to ``MANIFEST.in`` syntax is available at the
+:doc:`PyPA's Packaging User Guide <PyPUG:guides/using-manifest-in>`.
+
+Once the correct files are present in the ``sdist``, they can then be used by
+binary extensions during the build process, or included in the final
+:term:`wheel <Wheel>` [#build-process]_ if you configure ``setuptools`` with
+``include_package_data=True``.
+
+.. important::
+ Please note that, when using ``include_package_data=True``, only files **inside
+ the package directory** are included in the final ``wheel``, by default.
+
+ So for example, if you create a :term:`Python project <Project>` that uses
+ :pypi:`setuptools-scm` and have a ``tests`` directory outside of the package
+ folder, the ``tests`` directory will be present in the ``sdist`` but not in the
+ ``wheel`` [#wheel-vs-sdist]_.
+
+ See :doc:`/userguide/datafiles` for more information.
+
+----
+
+.. [#build-process]
+ You can think about the build process as two stages: first the ``sdist``
+ will be created and then the ``wheel`` will be produced from that ``sdist``.
+
+.. [#wheel-vs-sdist]
+ This happens because the ``sdist`` can contain files that are useful during
+ development or the build process itself, but not in runtime (e.g. tests,
+ docs, examples, etc...).
+ The ``wheel``, on the other hand, is a file format that has been optimized
+ and is ready to be unpacked into a running installation of Python or
+ :term:`Virtual Environment`.
+ Therefore it only contains items that are required during runtime.
include_package_data = True
This tells setuptools to install any data files it finds in your packages.
-The data files must be specified via the distutils' ``MANIFEST.in`` file.
+The data files must be specified via the distutils' |MANIFEST.in|_ file
+or automatically added by a :ref:`Revision Control System plugin
+<Adding Support for Revision Control Systems>`.
For more details, see :doc:`datafiles`
Packaging in Python can be hard and is constantly evolving.
`Python Packaging User Guide <https://packaging.python.org>`_ has tutorials and
up-to-date references that can help you when it is time to distribute your work.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
[metadata]
name = setuptools
-version = 60.9.3
+version = 60.10.0
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
pip>=19.1 # For proper file:// URLs support.
jaraco.envs>=2.2
pytest-xdist
- sphinx>=4.3.2
jaraco.path>=3.2.0
build[virtualenv]
filelock>=3.4.0
Metadata-Version: 2.1
Name: setuptools
-Version: 60.9.3
+Version: 60.10.0
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: https://github.com/pypa/setuptools
Author: Python Packaging Authority
setuptools/_vendor/more_itertools/more.py
setuptools/_vendor/more_itertools/recipes.py
setuptools/_vendor/more_itertools-8.8.0.dist-info/top_level.txt
+setuptools/_vendor/nspektr/__init__.py
+setuptools/_vendor/nspektr/_compat.py
+setuptools/_vendor/nspektr-0.3.0.dist-info/top_level.txt
setuptools/_vendor/ordered_set-3.1.1.dist-info/top_level.txt
setuptools/_vendor/packaging/__about__.py
setuptools/_vendor/packaging/__init__.py
setuptools/tests/test_sdist.py
setuptools/tests/test_setopt.py
setuptools/tests/test_setuptools.py
-setuptools/tests/test_sphinx_upload_docs.py
setuptools/tests/test_test.py
setuptools/tests/test_unicode_utils.py
setuptools/tests/test_upload.py
-setuptools/tests/test_upload_docs.py
setuptools/tests/test_virtualenv.py
setuptools/tests/test_wheel.py
setuptools/tests/test_windows_wrappers.py
pip>=19.1
jaraco.envs>=2.2
pytest-xdist
-sphinx>=4.3.2
jaraco.path>=3.2.0
build[virtualenv]
filelock>=3.4.0
--- /dev/null
+import itertools
+import functools
+import contextlib
+
+from setuptools.extern.packaging.requirements import Requirement
+from setuptools.extern.packaging.version import Version
+from setuptools.extern.more_itertools import always_iterable
+from setuptools.extern.jaraco.context import suppress
+from setuptools.extern.jaraco.functools import apply
+
+from ._compat import metadata, repair_extras
+
+
+def resolve(req: Requirement) -> metadata.Distribution:
+ """
+ Resolve the requirement to its distribution.
+
+ Ignore exception detail for Python 3.9 compatibility.
+
+ >>> resolve(Requirement('pytest<3')) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ importlib.metadata.PackageNotFoundError: No package metadata was found for pytest<3
+ """
+ dist = metadata.distribution(req.name)
+ if not req.specifier.contains(Version(dist.version), prereleases=True):
+ raise metadata.PackageNotFoundError(str(req))
+ dist.extras = req.extras # type: ignore
+ return dist
+
+
+@apply(bool)
+@suppress(metadata.PackageNotFoundError)
+def is_satisfied(req: Requirement):
+ return resolve(req)
+
+
+unsatisfied = functools.partial(itertools.filterfalse, is_satisfied)
+
+
+class NullMarker:
+ @classmethod
+ def wrap(cls, req: Requirement):
+ return req.marker or cls()
+
+ def evaluate(self, *args, **kwargs):
+ return True
+
+
+def find_direct_dependencies(dist, extras=None):
+ """
+ Find direct, declared dependencies for dist.
+ """
+ simple = (
+ req
+ for req in map(Requirement, always_iterable(dist.requires))
+ if NullMarker.wrap(req).evaluate(dict(extra=None))
+ )
+ extra_deps = (
+ req
+ for req in map(Requirement, always_iterable(dist.requires))
+ for extra in always_iterable(getattr(dist, 'extras', extras))
+ if NullMarker.wrap(req).evaluate(dict(extra=extra))
+ )
+ return itertools.chain(simple, extra_deps)
+
+
+def traverse(items, visit):
+ """
+ Given an iterable of items, traverse the items.
+
+ For each item, visit is called to return any additional items
+ to include in the traversal.
+ """
+ while True:
+ try:
+ item = next(items)
+ except StopIteration:
+ return
+ yield item
+ items = itertools.chain(items, visit(item))
+
+
+def find_req_dependencies(req):
+ with contextlib.suppress(metadata.PackageNotFoundError):
+ dist = resolve(req)
+ yield from find_direct_dependencies(dist)
+
+
+def find_dependencies(dist, extras=None):
+ """
+ Find all reachable dependencies for dist.
+
+ dist is an importlib.metadata.Distribution (or similar).
+ TODO: create a suitable protocol for type hint.
+
+ >>> deps = find_dependencies(resolve(Requirement('nspektr')))
+ >>> all(isinstance(dep, Requirement) for dep in deps)
+ True
+ >>> not any('pytest' in str(dep) for dep in deps)
+ True
+ >>> test_deps = find_dependencies(resolve(Requirement('nspektr[testing]')))
+ >>> any('pytest' in str(dep) for dep in test_deps)
+ True
+ """
+
+ def visit(req, seen=set()):
+ if req in seen:
+ return ()
+ seen.add(req)
+ return find_req_dependencies(req)
+
+ return traverse(find_direct_dependencies(dist, extras), visit)
+
+
+class Unresolved(Exception):
+ def __iter__(self):
+ return iter(self.args[0])
+
+
+def missing(ep):
+ """
+ Generate the unresolved dependencies (if any) of ep.
+ """
+ return unsatisfied(find_dependencies(ep.dist, repair_extras(ep.extras)))
+
+
+def check(ep):
+ """
+ >>> ep, = metadata.entry_points(group='console_scripts', name='pip')
+ >>> check(ep)
+ >>> dist = metadata.distribution('nspektr')
+
+ Since 'docs' extras are not installed, requesting them should fail.
+
+ >>> ep = metadata.EntryPoint(
+ ... group=None, name=None, value='nspektr [docs]')._for(dist)
+ >>> check(ep)
+ Traceback (most recent call last):
+ ...
+ nspektr.Unresolved: [...]
+ """
+ missed = list(missing(ep))
+ if missed:
+ raise Unresolved(missed)
--- /dev/null
+import contextlib
+import sys
+
+
+if sys.version_info >= (3, 10):
+ import importlib.metadata as metadata
+else:
+ import setuptools.extern.importlib_metadata as metadata # type: ignore # noqa: F401
+
+
+def repair_extras(extras):
+ """
+ Repair extras that appear as match objects.
+
+ python/importlib_metadata#369 revealed a flaw in the EntryPoint
+ implementation. This function wraps the extras to ensure
+ they are proper strings even on older implementations.
+ """
+ with contextlib.suppress(AttributeError):
+ return list(item.group(0) for item in extras)
+ return extras
jaraco.text==3.7.0
importlib_resources==5.4.0
importlib_metadata==4.11.1
+nspektr==0.3.0
# required for importlib_metadata on older Pythons
typing_extensions==4.0.1
# required for importlib_resources and _metadata on older Pythons
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
__all__ = [
- 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
+ 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
'get_exe_prefixes',
]
return struct.calcsize("P") == 8
-def samefile(p1, p2):
- """
- Determine if two paths reference the same file.
-
- Augments os.path.samefile to work on Windows and
- suppresses errors if the path doesn't exist.
- """
- both_exist = os.path.exists(p1) and os.path.exists(p2)
- use_samefile = hasattr(os.path, 'samefile') and both_exist
- if use_samefile:
- return os.path.samefile(p1, p2)
- norm_p1 = os.path.normpath(os.path.normcase(p1))
- norm_p2 = os.path.normpath(os.path.normcase(p2))
- return norm_p1 == norm_p2
-
-
def _to_bytes(s):
return s.encode('utf8')
self.script_dir = self.install_scripts
# default --record from the install command
self.set_undefined_options('install', ('record', 'record'))
- # Should this be moved to the if statement below? It's not used
- # elsewhere
- normpath = map(normalize_path, sys.path)
self.all_site_dirs = get_site_dirs()
- if self.site_dirs is not None:
- site_dirs = [
- os.path.expanduser(s.strip()) for s in
- self.site_dirs.split(',')
- ]
- for d in site_dirs:
- if not os.path.isdir(d):
- log.warn("%s (in --site-dirs) does not exist", d)
- elif normalize_path(d) not in normpath:
- raise DistutilsOptionError(
- d + " (in --site-dirs) is not on sys.path"
- )
- else:
- self.all_site_dirs.append(normalize_path(d))
+ self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
+
if not self.editable:
self.check_site_dir()
self.index_url = self.index_url or "https://pypi.org/simple/"
if not self.no_find_links:
self.package_index.add_find_links(self.find_links)
self.set_undefined_options('install_lib', ('optimize', 'optimize'))
- if not isinstance(self.optimize, int):
- try:
- self.optimize = int(self.optimize)
- if not (0 <= self.optimize <= 2):
- raise ValueError
- except ValueError as e:
- raise DistutilsOptionError(
- "--optimize must be 0, 1, or 2"
- ) from e
+ self.optimize = self._validate_optimize(self.optimize)
if self.editable and not self.build_directory:
raise DistutilsArgError(
self.outputs = []
+ @staticmethod
+ def _process_site_dirs(site_dirs):
+ if site_dirs is None:
+ return
+
+ normpath = map(normalize_path, sys.path)
+ site_dirs = [
+ os.path.expanduser(s.strip()) for s in
+ site_dirs.split(',')
+ ]
+ for d in site_dirs:
+ if not os.path.isdir(d):
+ log.warn("%s (in --site-dirs) does not exist", d)
+ elif normalize_path(d) not in normpath:
+ raise DistutilsOptionError(
+ d + " (in --site-dirs) is not on sys.path"
+ )
+ else:
+ yield normalize_path(d)
+
+ @staticmethod
+ def _validate_optimize(value):
+ try:
+ value = int(value)
+ if value not in range(3):
+ raise ValueError
+ except ValueError as e:
+ raise DistutilsOptionError(
+ "--optimize must be 0, 1, or 2"
+ ) from e
+
+ return value
+
def _fix_install_dir_for_user_site(self):
"""
Fix the install_dir if "--user" was used.
ensure_directory(destination)
dist = self.egg_distribution(egg_path)
- if not samefile(egg_path, destination):
+ if not (
+ os.path.exists(destination) and os.path.samefile(egg_path, destination)
+ ):
if os.path.isdir(destination) and not os.path.islink(destination):
dir_util.remove_tree(destination, dry_run=self.dry_run)
elif os.path.exists(destination):
if new_path:
self.paths.append(dist.location)
self.dirty = True
- Environment.add(self, dist)
+ super().add(dist)
def remove(self, dist):
"""Remove `dist` from the distribution map"""
while dist.location in self.paths:
self.paths.remove(dist.location)
self.dirty = True
- Environment.remove(self, dist)
+ super().remove(dist)
def make_relative(self, path):
npath, last = os.path.split(normalize_path(path))
import functools
import http.client
import urllib.parse
+import warnings
from .._importlib import metadata
+from .. import SetuptoolsDeprecationWarning
from .upload import upload
zip_file.close()
def run(self):
+ warnings.warn(
+ "upload_docs is deprecated and will be removed in a future "
+ "version. Use tools like httpie or curl instead.",
+ SetuptoolsDeprecationWarning,
+ )
+
# Run sub commands
for cmd_name in self.get_sub_commands():
self.run_command(cmd_name)
from setuptools.extern import packaging
from setuptools.extern import ordered_set
-from setuptools.extern.more_itertools import unique_everseen, always_iterable
+from setuptools.extern.more_itertools import unique_everseen
+from setuptools.extern import nspektr
from ._importlib import metadata
from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration
import pkg_resources
-from setuptools.extern.packaging import version, requirements
+from setuptools.extern.packaging import version
from . import _reqs
from . import _entry_points
Given an entry point, ensure that any declared extras for
its distribution are installed.
"""
- reqs = {
- req
- for req in map(requirements.Requirement, always_iterable(ep.dist.requires))
- for extra in ep.extras
- if extra in req.extras
- }
- missing = itertools.filterfalse(self._is_installed, reqs)
- for req in missing:
+ for req in nspektr.missing(ep):
# fetch_build_egg expects pkg_resources.Requirement
self.fetch_build_egg(pkg_resources.Requirement(str(req)))
- def _is_installed(self, req):
- try:
- dist = metadata.distribution(req.name)
- except metadata.PackageNotFoundError:
- return False
- found_ver = packaging.version.Version(dist.version())
- return found_ver in req.specifier
-
def get_egg_cache_dir(self):
egg_cache_dir = os.path.join(os.curdir, '.eggs')
if not os.path.exists(egg_cache_dir):
names = (
'packaging', 'pyparsing', 'ordered_set', 'more_itertools', 'importlib_metadata',
- 'zipp', 'importlib_resources', 'jaraco', 'typing_extensions',
+ 'zipp', 'importlib_resources', 'jaraco', 'typing_extensions', 'nspektr',
)
VendorImporter(__name__, names, 'setuptools._vendor').install()
# Make sure the file has been downloaded to the temp dir.
if os.path.dirname(filename) != tmpdir:
dst = os.path.join(tmpdir, basename)
- from setuptools.command.easy_install import samefile
- if not samefile(filename, dst):
+ if not (os.path.exists(dst) and os.path.samefile(filename, dst)):
shutil.copy2(filename, dst)
filename = dst
def run(self, cmd, *args, **kwargs):
cmd = [self.exe(cmd[0])] + cmd[1:]
kwargs = {"cwd": self.root, **kwargs} # Allow overriding
+ # In some environments (eg. downstream distro packaging), where:
+ # - tox isn't used to run tests and
+ # - PYTHONPATH is set to point to a specific setuptools codebase and
+ # - no custom env is explicitly set by a test
+ # PYTHONPATH will leak into the spawned processes.
+ # In that case tests look for module in the wrong place (on PYTHONPATH).
+ # Unless the test sets its own special env, pass a copy of the existing
+ # environment with removed PYTHONPATH to the subprocesses.
+ if "env" not in kwargs:
+ env = dict(os.environ)
+ if "PYTHONPATH" in env:
+ del env["PYTHONPATH"]
+ kwargs["env"] = env
return subprocess.check_output(cmd, *args, **kwargs)
+import os
import contextlib
import sys
import subprocess
+from pathlib import Path
import pytest
import path
@pytest.fixture(scope="session")
def setuptools_sdist(tmp_path_factory, request):
+ if os.getenv("PRE_BUILT_SETUPTOOLS_SDIST"):
+ return Path(os.getenv("PRE_BUILT_SETUPTOOLS_SDIST")).resolve()
+
with contexts.session_locked_tmp_dir(
request, tmp_path_factory, "sdist_build") as tmp:
dist = next(tmp.glob("*.tar.gz"), None)
@pytest.fixture(scope="session")
def setuptools_wheel(tmp_path_factory, request):
+ if os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL"):
+ return Path(os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL")).resolve()
+
with contexts.session_locked_tmp_dir(
request, tmp_path_factory, "wheel_build") as tmp:
dist = next(tmp.glob("*.whl"), None)
env = environment.VirtualEnv()
env.root = path.Path(tmp_path / 'venv')
env.req = str(setuptools_wheel)
- return env.create()
+ # In some environments (eg. downstream distro packaging),
+ # where tox isn't used to run tests and PYTHONPATH is set to point to
+ # a specific setuptools codebase, PYTHONPATH will leak into the spawned
+ # processes.
+ # env.create() should install the just created setuptools
+ # wheel, but it doesn't if it finds another existing matching setuptools
+ # installation present on PYTHONPATH:
+ # `setuptools is already installed with the same version as the provided
+ # wheel. Use --force-reinstall to force an installation of the wheel.`
+ # This prevents leaking PYTHONPATH to the created environment.
+ with contexts.environment(PYTHONPATH=None):
+ return env.create()
@pytest.fixture
import os
+import sys
import shutil
import signal
import tarfile
TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180")) # in seconds
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+pytestmark = pytest.mark.skipif(
+ sys.platform == "win32" and IS_PYPY,
+ reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor "
+ "is flaky and problematic"
+)
class BuildBackendBase:
self.pool.shutdown(wait=False) # doesn't stop already running processes
self._kill(pid)
pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)")
+ except (futures.process.BrokenProcessPool, MemoryError, OSError):
+ if IS_PYPY:
+ pytest.xfail("PyPy frequently fails tests with ProcessPoolExector")
+ raise
return method
+++ /dev/null
-import pytest
-
-from jaraco import path
-
-from setuptools.command.upload_docs import upload_docs
-from setuptools.dist import Distribution
-
-
-@pytest.fixture
-def sphinx_doc_sample_project(tmpdir_cwd):
- path.build({
- 'setup.py': 'from setuptools import setup; setup()',
- 'build': {
- 'docs': {
- 'conf.py': 'project="test"',
- 'index.rst': ".. toctree::\
- :maxdepth: 2\
- :caption: Contents:",
- },
- },
- })
-
-
-@pytest.mark.usefixtures('sphinx_doc_sample_project')
-class TestSphinxUploadDocs:
- def test_sphinx_doc(self):
- params = dict(
- packages=['test'],
- )
- dist = Distribution(params)
-
- cmd = upload_docs(dist)
-
- cmd.initialize_options()
- assert cmd.upload_dir is None
- assert cmd.has_sphinx() is True
- cmd.finalize_options()
+++ /dev/null
-import os
-import zipfile
-import contextlib
-
-import pytest
-from jaraco import path
-
-from setuptools.command.upload_docs import upload_docs
-from setuptools.dist import Distribution
-
-from .textwrap import DALS
-from . import contexts
-
-
-@pytest.fixture
-def sample_project(tmpdir_cwd):
- path.build({
- 'setup.py': DALS("""
- from setuptools import setup
-
- setup()
- """),
- 'build': {
- 'index.html': 'Hello world.',
- 'empty': {},
- }
- })
-
-
-@pytest.mark.usefixtures('sample_project')
-@pytest.mark.usefixtures('user_override')
-class TestUploadDocsTest:
- def test_create_zipfile(self):
- """
- Ensure zipfile creation handles common cases, including a folder
- containing an empty folder.
- """
-
- dist = Distribution()
-
- cmd = upload_docs(dist)
- cmd.target_dir = cmd.upload_dir = 'build'
- with contexts.tempdir() as tmp_dir:
- tmp_file = os.path.join(tmp_dir, 'foo.zip')
- zip_file = cmd.create_zipfile(tmp_file)
-
- assert zipfile.is_zipfile(tmp_file)
-
- with contextlib.closing(zipfile.ZipFile(tmp_file)) as zip_file:
- assert zip_file.namelist() == ['index.html']
-
- def test_build_multipart(self):
- data = dict(
- a="foo",
- b="bar",
- file=('file.txt', b'content'),
- )
- body, content_type = upload_docs._build_multipart(data)
- assert 'form-data' in content_type
- assert "b'" not in content_type
- assert 'b"' not in content_type
- assert isinstance(body, bytes)
- assert b'foo' in body
- assert b'content' in body
import os
import sys
-import itertools
import subprocess
+from urllib.request import urlopen
+from urllib.error import URLError
import pathlib
venv_without_setuptools.run(cmd)
-def _get_pip_versions():
- # This fixture will attempt to detect if tests are being run without
- # network connectivity and if so skip some tests
-
- network = True
+def access_pypi():
+ # Detect if tests are being run without connectivity
if not os.environ.get('NETWORK_REQUIRED', False): # pragma: nocover
try:
- from urllib.request import urlopen
- from urllib.error import URLError
- except ImportError:
- from urllib2 import urlopen, URLError # Python 2.7 compat
-
- try:
urlopen('https://pypi.org', timeout=1)
except URLError:
# No network, disable most of these tests
- network = False
+ return False
- def mark(param, *marks):
- if not isinstance(param, type(pytest.param(''))):
- param = pytest.param(param)
- return param._replace(marks=param.marks + marks)
+ return True
- def skip_network(param):
- return param if network else mark(param, pytest.mark.skip(reason="no network"))
- network_versions = [
- mark('pip<20', pytest.mark.xfail(reason='pypa/pip#6599')),
+@pytest.mark.skipif(
+ 'platform.python_implementation() == "PyPy"',
+ reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995",
+)
+@pytest.mark.skipif(not access_pypi(), reason="no network")
+# ^-- Even when it is not necessary to install a different version of `pip`
+# the build process will still try to download `wheel`, see #3147 and #2986.
+@pytest.mark.parametrize(
+ 'pip_version',
+ [
+ None,
+ pytest.param('pip<20', marks=pytest.mark.xfail(reason='pypa/pip#6599')),
'pip<20.1',
'pip<21',
'pip<22',
- mark(
+ pytest.param(
'https://github.com/pypa/pip/archive/main.zip',
- pytest.mark.xfail(reason='#2975'),
+ marks=pytest.mark.xfail(reason='#2975'),
),
]
-
- versions = itertools.chain(
- [None],
- map(skip_network, network_versions)
- )
-
- return list(versions)
-
-
-@pytest.mark.skipif(
- 'platform.python_implementation() == "PyPy"',
- reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995",
)
-@pytest.mark.parametrize('pip_version', _get_pip_versions())
def test_pip_upgrade_from_source(pip_version, venv_without_setuptools,
setuptools_wheel, setuptools_sdist):
"""
cmd = [
sys.executable, '-m',
'towncrier',
+ 'build',
'--version', get_version(),
'--yes',
]
more_file.write_text(text)
+def rewrite_nspektr(pkg_files: Path, new_root):
+ for file in pkg_files.glob('*.py'):
+ text = file.read_text()
+ text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text)
+ text = re.sub(r' (jaraco\.\w+)', rf' {new_root}.\1', text)
+ text = re.sub(r' (packaging)', rf' {new_root}.\1', text)
+ text = re.sub(r' (importlib_metadata)', rf' {new_root}.\1', text)
+ file.write_text(text)
+
+
def clean(vendor):
"""
Remove all files out of the vendor directory except the meta
rewrite_importlib_resources(vendor / 'importlib_resources', 'setuptools.extern')
rewrite_importlib_metadata(vendor / 'importlib_metadata', 'setuptools.extern')
rewrite_more_itertools(vendor / "more_itertools")
+ rewrite_nspektr(vendor / "nspektr", 'setuptools.extern')
__name__ == '__main__' and update_vendored()
extras = testing
passenv =
SETUPTOOLS_USE_DISTUTILS
+ PRE_BUILT_SETUPTOOLS_WHEEL
+ PRE_BUILT_SETUPTOOLS_SDIST
TIMEOUT_BACKEND_TEST # timeout (in seconds) for test_build_meta
windir # required for test_pkg_resources
# honor git config in pytest-perf