install: skip
script: skip
after_success: true
- before_deploy: python bootstrap.py
+ before_deploy:
+ - python bootstrap.py
+ - "! grep pyc setuptools.egg-info/SOURCES.txt"
deploy:
provider: pypi
on:
# update egg_info based on setup.py in checkout
- python bootstrap.py
+- "! grep pyc setuptools.egg-info/SOURCES.txt"
script:
- |
+v40.6.0
+-------
+
+* #1541: Officially deprecated the ``requires`` parameter in ``setup()``.
+* #1519: In ``pkg_resources.normalize_path``, additional path normalization is now performed to ensure path values to a directory is always the same, preventing false positives when checking scripts have a consistent prefix to set up on Windows.
+* #1545: Changed the warning class of all deprecation warnings; deprecation warning classes are no longer derived from ``DeprecationWarning`` and are thus visible by default.
+* #1554: ``build_meta.build_sdist`` now includes ``setup.py`` in source distributions by default.
+* #1576: Started monkey-patching ``get_metadata_version`` and ``read_pkg_file`` onto ``distutils.DistributionMetadata`` to retain the correct version on the ``PKG-INFO`` file in the (deprecated) ``upload`` command.
+* #1533: Restricted the ``recursive-include setuptools/_vendor`` to contain only .py and .txt files.
+* #1395: Changed Pyrex references to Cython in the documentation.
+* #1456: Documented that the ``rpmbuild`` packages is required for the ``bdist_rpm`` command.
+* #1537: Documented how to use ``setup.cfg`` for ``src/ layouts``
+* #1539: Added minimum version column in ``setup.cfg`` metadata table.
+* #1552: Fixed a minor typo in the python 2/3 compatibility documentation.
+* #1553: Updated installation instructions to point to ``pip install`` instead of ``ez_setup.py``.
+* #1560: Updated ``setuptools`` distribution documentation to remove some outdated information.
+* #1564: Documented ``setup.cfg`` minimum version for version and project_urls.
+* #1572: Added the ``concurrent.futures`` backport ``futures`` to the Python 2.7 test suite requirements.
+
+
v40.5.0
-------
recursive-include tests *.py
recursive-include setuptools/tests *.html
recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
-recursive-include setuptools/_vendor *
+recursive-include setuptools/_vendor *.py *.txt
recursive-include pkg_resources *.py *.txt
include *.py
include *.rst
User's Guide for instructions on installing, upgrading, and uninstalling
Setuptools.
-The project is `maintained at GitHub <https://github.com/pypa/setuptools>`_
-by the `Setuptools Developers
-<https://github.com/orgs/pypa/teams/setuptools-developers>`_.
-
Questions and comments should be directed to the `distutils-sig
mailing list <http://mail.python.org/pipermail/distutils-sig/>`_.
Bug reports and especially tested patches may be
--- /dev/null
+:orphan:
+
+``ez_setup`` distribution guide
+===============================
+
+Using ``setuptools``... Without bundling it!
+---------------------------------------------
+
+.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+
+.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
+
+.. _EasyInstall Installation Instructions: easy_install.html
+
+.. _Custom Installation Locations: easy_install.html
+
+Your users might not have ``setuptools`` installed on their machines, or even
+if they do, it might not be the right version. Fixing this is easy; just
+download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
+script. (Be sure to add it to your revision control system, too.) Then add
+these two lines to the very top of your setup script, before the script imports
+anything from setuptools:
+
+.. code-block:: python
+
+ import ez_setup
+ ez_setup.use_setuptools()
+
+That's it. The ``ez_setup`` module will automatically download a matching
+version of ``setuptools`` from PyPI, if it isn't present on the target system.
+Whenever you install an updated version of setuptools, you should also update
+your projects' ``ez_setup.py`` files, so that a matching version gets installed
+on the target machine(s).
+
+(By the way, if you need to distribute a specific version of ``setuptools``,
+you can specify the exact version and base download URL as parameters to the
+``use_setuptools()`` function. See the function's docstring for details.)
+
+
+What Your Users Should Know
+---------------------------
+
+In general, a setuptools-based project looks just like any distutils-based
+project -- as long as your users have an internet connection and are installing
+to ``site-packages``, that is. But for some users, these conditions don't
+apply, and they may become frustrated if this is their first encounter with
+a setuptools-based project. To keep these users happy, you should review the
+following topics in your project's installation instructions, if they are
+relevant to your project and your target audience isn't already familiar with
+setuptools and ``easy_install``.
+
+Network Access
+ If your project is using ``ez_setup``, you should inform users of the
+ need to either have network access, or to preinstall the correct version of
+ setuptools using the `EasyInstall installation instructions`_. Those
+ instructions also have tips for dealing with firewalls as well as how to
+ manually download and install setuptools.
+
+Custom Installation Locations
+ You should inform your users that if they are installing your project to
+ somewhere other than the main ``site-packages`` directory, they should
+ first install setuptools using the instructions for `Custom Installation
+ Locations`_, before installing your project.
+
+Your Project's Dependencies
+ If your project depends on other projects that may need to be downloaded
+ from PyPI or elsewhere, you should list them in your installation
+ instructions, or tell users how to find out what they are. While most
+ users will not need this information, any users who don't have unrestricted
+ internet access may have to find, download, and install the other projects
+ manually. (Note, however, that they must still install those projects
+ using ``easy_install``, or your project will not know they are installed,
+ and your setup script will try to download them again.)
+
+ If you want to be especially friendly to users with limited network access,
+ you may wish to build eggs for your project and its dependencies, making
+ them all available for download from your site, or at least create a page
+ with links to all of the needed eggs. In this way, users with limited
+ network access can manually download all the eggs to a single directory,
+ then use the ``-f`` option of ``easy_install`` to specify the directory
+ to find eggs in. Users who have full network access can just use ``-f``
+ with the URL of your download page, and ``easy_install`` will find all the
+ needed eggs using your links directly. This is also useful when your
+ target audience isn't able to compile packages (e.g. most Windows users)
+ and your package or some of its dependencies include C code.
+
+Revision Control System Users and Co-Developers
+ Users and co-developers who are tracking your in-development code using
+ a revision control system should probably read this manual's sections
+ regarding such development. Alternately, you may wish to create a
+ quick-reference guide containing the tips from this manual that apply to
+ your particular situation. For example, if you recommend that people use
+ ``setup.py develop`` when tracking your in-development code, you should let
+ them know that this needs to be run after every update or commit.
+
+ Similarly, if you remove modules or data files from your project, you
+ should remind them to run ``setup.py clean --all`` and delete any obsolete
+ ``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not
+ just setuptools, but not everybody knows about them; be kind to your users
+ by spelling out your project's best practices rather than leaving them
+ guessing.)
+
+Creating System Packages
+ Some users want to manage all Python packages using a single package
+ manager, and sometimes that package manager isn't ``easy_install``!
+ Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
+ ``bdist_dumb`` formats for system packaging. If a user has a locally-
+ installed "bdist" packaging tool that internally uses the distutils
+ ``install`` command, it should be able to work with ``setuptools``. Some
+ examples of "bdist" formats that this should work with include the
+ ``bdist_nsi`` and ``bdist_msi`` formats for Windows.
+
+ However, packaging tools that build binary distributions by running
+ ``setup.py install`` on the command line or as a subprocess will require
+ modification to work with setuptools. They should use the
+ ``--single-version-externally-managed`` option to the ``install`` command,
+ combined with the standard ``--root`` or ``--record`` options.
+ See the `install command`_ documentation below for more details. The
+ ``bdist_deb`` command is an example of a command that currently requires
+ this kind of patching to work with setuptools.
+
+ Please note that building system packages may require you to install
+ some system software, for example ``bdist_rpm`` requires the ``rpmbuild``
+ command to be installed.
+
+ If you or your users have a problem building a usable system package for
+ your project, please report the problem via the mailing list so that
+ either the "bdist" tool in question or setuptools can be modified to
+ resolve the issue.
+
+Your users might not have ``setuptools`` installed on their machines, or even
+if they do, it might not be the right version. Fixing this is easy; just
+download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
+script. (Be sure to add it to your revision control system, too.) Then add
+these two lines to the very top of your setup script, before the script imports
+anything from setuptools:
+
+.. code-block:: python
+
+ import ez_setup
+ ez_setup.use_setuptools()
+
+That's it. The ``ez_setup`` module will automatically download a matching
+version of ``setuptools`` from PyPI, if it isn't present on the target system.
+Whenever you install an updated version of setuptools, you should also update
+your projects' ``ez_setup.py`` files, so that a matching version gets installed
+on the target machine(s).
+
+(By the way, if you need to distribute a specific version of ``setuptools``,
+you can specify the exact version and base download URL as parameters to the
+``use_setuptools()`` function. See the function's docstring for details.)
+
+.. _install command:
+
+``install`` - Run ``easy_install`` or old-style installation
+============================================================
+
+The setuptools ``install`` command is basically a shortcut to run the
+``easy_install`` command on the current project. However, for convenience
+in creating "system packages" of setuptools-based projects, you can also
+use this option:
+
+``--single-version-externally-managed``
+ This boolean option tells the ``install`` command to perform an "old style"
+ installation, with the addition of an ``.egg-info`` directory so that the
+ installed project will still have its metadata available and operate
+ normally. If you use this option, you *must* also specify the ``--root``
+ or ``--record`` options (or both), because otherwise you will have no way
+ to identify and remove the installed files.
+
+This option is automatically in effect when ``install`` is invoked by another
+distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
+will create system packages of eggs. It is also automatically in effect if
+you specify the ``--root`` option.
+
+
+``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
+==============================================================================
+
+Setuptools runs this command as part of ``install`` operations that use the
+``--single-version-externally-managed`` options. You should not invoke it
+directly; it is documented here for completeness and so that distutils
+extensions such as system package builders can make use of it. This command
+has only one option:
+
+``--install-dir=DIR, -d DIR``
+ The parent directory where the ``.egg-info`` directory will be placed.
+ Defaults to the same as the ``--install-dir`` option specified for the
+ ``install_lib`` command, which is usually the system ``site-packages``
+ directory.
+
+This command assumes that the ``egg_info`` command has been given valid options
+via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
+command and use its options to locate the project's source ``.egg-info``
+directory.
:maxdepth: 2
setuptools
- easy_install
pkg_resources
python3
development
roadmap
+ Deprecated: Easy Install <easy_install>
history
Setuptools provides a facility to invoke 2to3 on the code as a part of the
build process, by setting the keyword parameter ``use_2to3`` to True, but
-the Setuptools strongly recommends instead developing a unified codebase
+the Setuptools project strongly recommends instead developing a unified codebase
using `six <https://pypi.org/project/six/>`_,
`future <https://pypi.org/project/future/>`_, or another compatibility
library.
Roadmap
=======
-Setuptools has the following large-scale goals on the roadmap:
-
-- Harmonize declarative config with pyproject.toml syntax.
-- Deprecate and remove setup_requires and easy_install in
- favor of PEP 518 build requirements and pip install.
-- Adopt the Distutils package and stop monkeypatching stdlib.
+Setuptools maintains a series of `milestones
+<https://github.com/pypa/setuptools/milestones>`_ to track
+a roadmap of large-scale goals.
files for any number of "main" functions in your project. (Note: this is not
a py2exe replacement; the .exe files rely on the local Python installation.)
-* Transparent Pyrex support, so that your setup.py can list ``.pyx`` files and
- still work even when the end-user doesn't have Pyrex installed (as long as
- you include the Pyrex-generated C in your source distribution)
+* Transparent Cython support, so that your setup.py can list ``.pyx`` files and
+ still work even when the end-user doesn't have Cython installed (as long as
+ you include the Cython-generated C in your source distribution)
* Command aliases - create project-specific, per-user, or site-wide shortcut
names for commonly used commands and options
Installing ``setuptools``
=========================
-Please follow the `EasyInstall Installation Instructions`_ to install the
-current stable version of setuptools. In particular, be sure to read the
-section on `Custom Installation Locations`_ if you are installing anywhere
-other than Python's ``site-packages`` directory.
+.. _EasyInstall Installation Instructions: easy_install.html
-.. _EasyInstall Installation Instructions: easy_install.html#installation-instructions
+.. _Custom Installation Locations: easy_install.html
-.. _Custom Installation Locations: easy_install.html#custom-installation-locations
+.. _Installing Packages: https://packaging.python.org/tutorials/installing-packages/
-If you want the current in-development version of setuptools, you should first
-install a stable version, and then run::
+To install the latest version of setuptools, use::
- ez_setup.py setuptools==dev
-
-This will download and install the latest development (i.e. unstable) version
-of setuptools from the Python Subversion sandbox.
+ pip install -U setuptools
+Refer to `Installing Packages`_ guide for more information.
Basic Use
=========
Distributing a ``setuptools``-based project
===========================================
-Using ``setuptools``... Without bundling it!
----------------------------------------------
+Detailed instructions to distribute a setuptools project can be found at
+`Packaging project tutorials`_.
-.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+.. _Packaging project tutorials: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives
-Your users might not have ``setuptools`` installed on their machines, or even
-if they do, it might not be the right version. Fixing this is easy; just
-download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
-script. (Be sure to add it to your revision control system, too.) Then add
-these two lines to the very top of your setup script, before the script imports
-anything from setuptools:
+Before you begin, make sure you have the latest versions of setuptools and wheel::
-.. code-block:: python
+ python3 -m pip install --user --upgrade setuptools wheel
- import ez_setup
- ez_setup.use_setuptools()
+To build a setuptools project, run this command from the same directory where
+setup.py is located::
-That's it. The ``ez_setup`` module will automatically download a matching
-version of ``setuptools`` from PyPI, if it isn't present on the target system.
-Whenever you install an updated version of setuptools, you should also update
-your projects' ``ez_setup.py`` files, so that a matching version gets installed
-on the target machine(s).
+ python3 setup.py sdist bdist_wheel
-(By the way, if you need to distribute a specific version of ``setuptools``,
-you can specify the exact version and base download URL as parameters to the
-``use_setuptools()`` function. See the function's docstring for details.)
+This will generate distribution archives in the `dist` directory.
+Before you upload the generated archives make sure you're registered on
+https://test.pypi.org/account/register/. You will also need to verify your email
+to be able to upload any packages.
+You should install twine to be able to upload packages::
-What Your Users Should Know
----------------------------
+ python3 -m pip install --user --upgrade setuptools wheel
+
+Now, to upload these archives, run::
+
+ twine upload --repository-url https://test.pypi.org/legacy/ dist/*
+
+To install your newly uploaded package ``example_pkg``, you can use pip::
+
+ python3 -m pip install --index-url https://test.pypi.org/simple/ example_pkg
+
+If you have issues at any point, please refer to `Packaging project tutorials`_
+for clarification.
+
+Distributing legacy ``setuptools`` projects using ez_setup.py
+-------------------------------------------------------------
+
+.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+
+Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is
+deprecated in favor of PIP. Please consider migrating to using pip and twine based
+distribution.
-In general, a setuptools-based project looks just like any distutils-based
-project -- as long as your users have an internet connection and are installing
-to ``site-packages``, that is. But for some users, these conditions don't
-apply, and they may become frustrated if this is their first encounter with
-a setuptools-based project. To keep these users happy, you should review the
-following topics in your project's installation instructions, if they are
-relevant to your project and your target audience isn't already familiar with
-setuptools and ``easy_install``.
-
-Network Access
- If your project is using ``ez_setup``, you should inform users of the
- need to either have network access, or to preinstall the correct version of
- setuptools using the `EasyInstall installation instructions`_. Those
- instructions also have tips for dealing with firewalls as well as how to
- manually download and install setuptools.
-
-Custom Installation Locations
- You should inform your users that if they are installing your project to
- somewhere other than the main ``site-packages`` directory, they should
- first install setuptools using the instructions for `Custom Installation
- Locations`_, before installing your project.
-
-Your Project's Dependencies
- If your project depends on other projects that may need to be downloaded
- from PyPI or elsewhere, you should list them in your installation
- instructions, or tell users how to find out what they are. While most
- users will not need this information, any users who don't have unrestricted
- internet access may have to find, download, and install the other projects
- manually. (Note, however, that they must still install those projects
- using ``easy_install``, or your project will not know they are installed,
- and your setup script will try to download them again.)
-
- If you want to be especially friendly to users with limited network access,
- you may wish to build eggs for your project and its dependencies, making
- them all available for download from your site, or at least create a page
- with links to all of the needed eggs. In this way, users with limited
- network access can manually download all the eggs to a single directory,
- then use the ``-f`` option of ``easy_install`` to specify the directory
- to find eggs in. Users who have full network access can just use ``-f``
- with the URL of your download page, and ``easy_install`` will find all the
- needed eggs using your links directly. This is also useful when your
- target audience isn't able to compile packages (e.g. most Windows users)
- and your package or some of its dependencies include C code.
-
-Revision Control System Users and Co-Developers
- Users and co-developers who are tracking your in-development code using
- a revision control system should probably read this manual's sections
- regarding such development. Alternately, you may wish to create a
- quick-reference guide containing the tips from this manual that apply to
- your particular situation. For example, if you recommend that people use
- ``setup.py develop`` when tracking your in-development code, you should let
- them know that this needs to be run after every update or commit.
-
- Similarly, if you remove modules or data files from your project, you
- should remind them to run ``setup.py clean --all`` and delete any obsolete
- ``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not
- just setuptools, but not everybody knows about them; be kind to your users
- by spelling out your project's best practices rather than leaving them
- guessing.)
-
-Creating System Packages
- Some users want to manage all Python packages using a single package
- manager, and sometimes that package manager isn't ``easy_install``!
- Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
- ``bdist_dumb`` formats for system packaging. If a user has a locally-
- installed "bdist" packaging tool that internally uses the distutils
- ``install`` command, it should be able to work with ``setuptools``. Some
- examples of "bdist" formats that this should work with include the
- ``bdist_nsi`` and ``bdist_msi`` formats for Windows.
-
- However, packaging tools that build binary distributions by running
- ``setup.py install`` on the command line or as a subprocess will require
- modification to work with setuptools. They should use the
- ``--single-version-externally-managed`` option to the ``install`` command,
- combined with the standard ``--root`` or ``--record`` options.
- See the `install command`_ documentation below for more details. The
- ``bdist_deb`` command is an example of a command that currently requires
- this kind of patching to work with setuptools.
-
- If you or your users have a problem building a usable system package for
- your project, please report the problem via the mailing list so that
- either the "bdist" tool in question or setuptools can be modified to
- resolve the issue.
+However, if you still have any ``ez_setup`` based packages, documentation for
+ez_setup based distributions can be found at `ez_setup distribution guide`_.
+.. _ez_setup distribution guide: ez_setup.html
Setting the ``zip_safe`` flag
-----------------------------
-Distributing Extensions compiled with Pyrex
--------------------------------------------
+Distributing Extensions compiled with Cython
+--------------------------------------------
-``setuptools`` includes transparent support for building Pyrex extensions, as
-long as you define your extensions using ``setuptools.Extension``, *not*
-``distutils.Extension``. You must also not import anything from Pyrex in
-your setup script.
+``setuptools`` will detect at build time whether Cython is installed or not.
+If Cython is not found ``setuptools`` will ignore pyx files.
+
+To ensure Cython is available, include Cython in the build-requires section
+of your pyproject.toml::
+
+ [build-system]
+ requires=[..., 'cython']
+
+Built with pip 10 or later, that declaration is sufficient to include Cython
+in the build. For broader compatibility, declare the dependency in your
+setup-requires of setup.cfg::
+
+ [options]
+ setup_requires =
+ ...
+ cython
+
+As long as Cython is present in the build environment, ``setuptools`` includes
+transparent support for building Cython extensions, as
+long as extensions are defined using ``setuptools.Extension``.
If you follow these rules, you can safely list ``.pyx`` files as the source
-of your ``Extension`` objects in the setup script. ``setuptools`` will detect
-at build time whether Pyrex is installed or not. If it is, then ``setuptools``
-will use it. If not, then ``setuptools`` will silently change the
-``Extension`` objects to refer to the ``.c`` counterparts of the ``.pyx``
-files, so that the normal distutils C compilation process will occur.
+of your ``Extension`` objects in the setup script. If it is, then ``setuptools``
+will use it.
Of course, for this to work, your source distributions must include the C
-code generated by Pyrex, as well as your original ``.pyx`` files. This means
+code generated by Cython, as well as your original ``.pyx`` files. This means
that you will probably want to include current ``.c`` files in your revision
control system, rebuilding them whenever you check changes in for the ``.pyx``
source files. This will ensure that people tracking your project in a revision
-control system will be able to build it even if they don't have Pyrex
+control system will be able to build it even if they don't have Cython
installed, and that your source releases will be similarly usable with or
-without Pyrex.
+without Cython.
-----------------
(Notice that ``egg_info`` must always appear on the command line *before* any
commands that you want the version changes to apply to.)
-
-.. _install command:
-
-``install`` - Run ``easy_install`` or old-style installation
-============================================================
-
-The setuptools ``install`` command is basically a shortcut to run the
-``easy_install`` command on the current project. However, for convenience
-in creating "system packages" of setuptools-based projects, you can also
-use this option:
-
-``--single-version-externally-managed``
- This boolean option tells the ``install`` command to perform an "old style"
- installation, with the addition of an ``.egg-info`` directory so that the
- installed project will still have its metadata available and operate
- normally. If you use this option, you *must* also specify the ``--root``
- or ``--record`` options (or both), because otherwise you will have no way
- to identify and remove the installed files.
-
-This option is automatically in effect when ``install`` is invoked by another
-distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
-will create system packages of eggs. It is also automatically in effect if
-you specify the ``--root`` option.
-
-
-``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
-==============================================================================
-
-Setuptools runs this command as part of ``install`` operations that use the
-``--single-version-externally-managed`` options. You should not invoke it
-directly; it is documented here for completeness and so that distutils
-extensions such as system package builders can make use of it. This command
-has only one option:
-
-``--install-dir=DIR, -d DIR``
- The parent directory where the ``.egg-info`` directory will be placed.
- Defaults to the same as the ``--install-dir`` option specified for the
- ``install_lib`` command, which is usually the system ``site-packages``
- directory.
-
-This command assumes that the ``egg_info`` command has been given valid options
-via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
-command and use its options to locate the project's source ``.egg-info``
-directory.
-
-
.. _rotate:
``rotate`` - Delete outdated distribution files
* Unknown keys are ignored.
+Using a ``src/`` layout
+=======================
+
+One commonly used package configuration has all the module source code in a
+subdirectory (often called the ``src/`` layout), like this::
+
+ ├── src
+ │  └── mypackage
+ │  ├── __init__.py
+ │  └── mod1.py
+ ├── setup.py
+ └── setup.cfg
+
+You can set up your ``setup.cfg`` to automatically find all your packages in
+the subdirectory like this:
+
+.. code-block:: ini
+
+ # This example contains just the necessary options for a src-layout, set up
+ # the rest of the file as described above.
+
+ [options]
+ package_dir=
+ =src
+ packages=find:
+
+ [options.packages.find]
+ where=src
+
Specifying values
=================
The aliases given below are supported for compatibility reasons,
but their use is not advised.
-============================== ================= =====
-Key Aliases Type
-============================== ================= =====
+============================== ================= ================= =============== =====
+Key Aliases Type Minumum Version Notes
+============================== ================= ================= =============== =====
name str
-version attr:, file:, str
+version attr:, file:, str 39.2.0 (1)
url home-page str
download_url download-url str
-project_urls dict
+project_urls dict 38.3.0
author str
author_email author-email str
maintainer str
license file:, str
description summary file:, str
long_description long-description file:, str
-long_description_content_type str
+long_description_content_type str 38.6.0
keywords list-comma
platforms platform list-comma
provides list-comma
requires list-comma
obsoletes list-comma
-============================== ================= =====
+============================== ================= ================= =============== =====
.. note::
A version loaded using the ``file:`` directive must comply with PEP 440.
It is easy to accidentally put something other than a valid version
string in such a file, so validation is stricter in this case.
+Notes:
+1. The `version` file attribute has only been supported since 39.2.0.
+
Options
-------
-======================= =====
-Key Type
-======================= =====
+======================= =================================== =============== =====
+Key Type Minimum Version Notes
+======================= =================================== =============== =====
zip_safe bool
setup_requires list-semi
install_requires list-semi
exclude_package_data section
namespace_packages list-comma
py_modules list-comma
-======================= =====
+data_files dict 40.6.0
+======================= =================================== =============== =====
.. note::
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.
'register_finder', 'register_namespace_handler', 'register_loader_type',
'fixup_namespace_packages', 'get_importer',
+ # Warnings
+ 'PkgResourcesDeprecationWarning',
+
# Deprecated/backward compatibility only
'run_main', 'AvailableDistributions',
]
def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes"""
- return os.path.normcase(os.path.realpath(_cygwin_patch(filename)))
+ return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover
warnings.warn(
"Parameters to load are deprecated. Call .resolve and "
".require separately.",
- DeprecationWarning,
+ PkgResourcesDeprecationWarning,
stacklevel=2,
)
if require:
# match order
list(map(working_set.add_entry, sys.path))
globals().update(locals())
+
+class PkgResourcesDeprecationWarning(Warning):
+ """
+ Base class for warning about deprecations in ``pkg_resources``
+
+ This class is not derived from ``DeprecationWarning``, and as such is
+ visible by default.
+ """
req = pkg_resources.Requirement.parse('foo>=1.9')
dist = pkg_resources.WorkingSet([env.paths['lib']]).find(req)
assert dist.version == version
+
+ @pytest.mark.parametrize(
+ 'unnormalized, normalized',
+ [
+ ('foo', 'foo'),
+ ('foo/', 'foo'),
+ ('foo/bar', 'foo/bar'),
+ ('foo/bar/', 'foo/bar'),
+ ],
+ )
+ def test_normalize_path_trailing_sep(self, unnormalized, normalized):
+ """Ensure the trailing slash is cleaned for path comparison.
+
+ See pypa/setuptools#1519.
+ """
+ result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
+ result_from_normalized = pkg_resources.normalize_path(normalized)
+ assert result_from_unnormalized == result_from_normalized
+
+ @pytest.mark.skipif(
+ os.path.normcase('A') != os.path.normcase('a'),
+ reason='Testing case-insensitive filesystems.',
+ )
+ @pytest.mark.parametrize(
+ 'unnormalized, normalized',
+ [
+ ('MiXeD/CasE', 'mixed/case'),
+ ],
+ )
+ def test_normalize_path_normcase(self, unnormalized, normalized):
+ """Ensure mixed case is normalized on case-insensitive filesystems.
+ """
+ result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
+ result_from_normalized = pkg_resources.normalize_path(normalized)
+ assert result_from_unnormalized == result_from_normalized
+
+ @pytest.mark.skipif(
+ os.path.sep != '\\',
+ reason='Testing systems using backslashes as path separators.',
+ )
+ @pytest.mark.parametrize(
+ 'unnormalized, expected',
+ [
+ ('forward/slash', 'forward\\slash'),
+ ('forward/slash/', 'forward\\slash'),
+ ('backward\\slash\\', 'backward\\slash'),
+ ],
+ )
+ def test_normalize_path_backslash_sep(self, unnormalized, expected):
+ """Ensure path seps are cleaned on backslash path sep systems.
+ """
+ result = pkg_resources.normalize_path(unnormalized)
+ assert result.endswith(expected)
from pkg_resources import (
parse_requirements, VersionConflict, parse_version,
Distribution, EntryPoint, Requirement, safe_version, safe_name,
- WorkingSet)
+ WorkingSet, PkgResourcesDeprecationWarning)
# from Python 3.6 docs.
with pytest.raises(ValueError):
EntryPoint.parse_map(self.submap_str)
+ def testDeprecationWarnings(self):
+ ep = EntryPoint(
+ "foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"],
+ ["x"]
+ )
+ with pytest.warns(pkg_resources.PkgResourcesDeprecationWarning):
+ ep.load(require=False)
+
+
class TestRequirements:
def testBasics(self):
[bumpversion]
-current_version = 40.5.0
+current_version = 40.6.0
commit = True
tag = True
setup_params = dict(
name="setuptools",
- version="40.5.0",
+ version="40.6.0",
description=(
"Easily download, build, install, upgrade, and uninstall "
"Python packages"
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
from distutils.util import convert_path
from fnmatch import fnmatchcase
+from ._deprecation_warning import SetuptoolsDeprecationWarning
+
from setuptools.extern.six import PY3
from setuptools.extern.six.moves import filter, map
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
+ 'SetuptoolsDeprecationWarning',
'find_packages'
]
return list(files)
+# Apply monkey patches
monkey.patch_all()
--- /dev/null
+class SetuptoolsDeprecationWarning(Warning):
+ """
+ Base class for warning deprecations in ``setuptools``
+
+ This class is not derived from ``DeprecationWarning``, and as such is
+ visible by default.
+ """
import shlex
import io
+
from sysconfig import get_config_vars, get_path
+from setuptools import SetuptoolsDeprecationWarning
+
from setuptools.extern import six
from setuptools.extern.six.moves import configparser, map
@classmethod
def get_script_args(cls, dist, executable=None, wininst=False):
# for backward compatibility
- warnings.warn("Use get_args", DeprecationWarning)
+ warnings.warn("Use get_args", EasyInstallDeprecationWarning)
writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
header = cls.get_script_header("", executable, wininst)
return writer.get_args(dist, header)
@classmethod
def get_script_header(cls, script_text, executable=None, wininst=False):
# for backward compatibility
- warnings.warn("Use get_header", DeprecationWarning, stacklevel=2)
+ warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
if wininst:
executable = "python.exe"
return cls.get_header(script_text, executable)
@classmethod
def get_writer(cls, force_windows):
# for backward compatibility
- warnings.warn("Use best", DeprecationWarning)
+ warnings.warn("Use best", EasyInstallDeprecationWarning)
return WindowsScriptWriter.best() if force_windows else cls.best()
@classmethod
@classmethod
def get_writer(cls):
# for backward compatibility
- warnings.warn("Use best", DeprecationWarning)
+ warnings.warn("Use best", EasyInstallDeprecationWarning)
return cls.best()
@classmethod
yield
finally:
distutils.core.gen_usage = saved
+
+class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning."""
+
from setuptools.glob import glob
from setuptools.extern import packaging
-
+from setuptools import SetuptoolsDeprecationWarning
def translate_pattern(glob):
"""
self.filelist.extend(rcfiles)
elif os.path.exists(self.manifest):
self.read_manifest()
+
+ if os.path.exists("setup.py"):
+ # setup.py should be included by default, even if it's not
+ # the script called to create the sdist
+ self.filelist.append("setup.py")
+
ei_cmd = self.get_finalized_command('egg_info')
self.filelist.graft(ei_cmd.egg_info)
Get a -r### off of PKG-INFO Version in case this is an sdist of
a subversion revision.
"""
- warnings.warn("get_pkg_info_revision is deprecated.", DeprecationWarning)
+ warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
if os.path.exists('PKG-INFO'):
with io.open('PKG-INFO') as f:
for line in f:
if match:
return int(match.group(1))
return 0
+
+
+class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning."""
+import io
+import os
+import hashlib
import getpass
+import platform
+
+from base64 import standard_b64encode
+
from distutils import log
from distutils.command import upload as orig
+from distutils.spawn import spawn
+
+from distutils.errors import DistutilsError
+from six.moves.urllib.request import urlopen, Request
+from six.moves.urllib.error import HTTPError
+from six.moves.urllib.parse import urlparse
class upload(orig.upload):
"""
Override default upload behavior to obtain password
in a variety of different ways.
"""
-
def run(self):
try:
orig.upload.run(self)
self._prompt_for_password()
)
+ def upload_file(self, command, pyversion, filename):
+ # Makes sure the repository URL is compliant
+ schema, netloc, url, params, query, fragments = \
+ urlparse(self.repository)
+ if params or query or fragments:
+ raise AssertionError("Incompatible url %s" % self.repository)
+
+ if schema not in ('http', 'https'):
+ raise AssertionError("unsupported schema " + schema)
+
+ # Sign if requested
+ if self.sign:
+ gpg_args = ["gpg", "--detach-sign", "-a", filename]
+ if self.identity:
+ gpg_args[2:2] = ["--local-user", self.identity]
+ spawn(gpg_args,
+ dry_run=self.dry_run)
+
+ # Fill in the data - send all the meta-data in case we need to
+ # register a new release
+ with open(filename, 'rb') as f:
+ content = f.read()
+
+ meta = self.distribution.metadata
+
+ data = {
+ # action
+ ':action': 'file_upload',
+ 'protocol_version': '1',
+
+ # identify release
+ 'name': meta.get_name(),
+ 'version': meta.get_version(),
+
+ # file content
+ 'content': (os.path.basename(filename),content),
+ 'filetype': command,
+ 'pyversion': pyversion,
+ 'md5_digest': hashlib.md5(content).hexdigest(),
+
+ # additional meta-data
+ 'metadata_version': str(meta.get_metadata_version()),
+ 'summary': meta.get_description(),
+ 'home_page': meta.get_url(),
+ 'author': meta.get_contact(),
+ 'author_email': meta.get_contact_email(),
+ 'license': meta.get_licence(),
+ 'description': meta.get_long_description(),
+ 'keywords': meta.get_keywords(),
+ 'platform': meta.get_platforms(),
+ 'classifiers': meta.get_classifiers(),
+ 'download_url': meta.get_download_url(),
+ # PEP 314
+ 'provides': meta.get_provides(),
+ 'requires': meta.get_requires(),
+ 'obsoletes': meta.get_obsoletes(),
+ }
+
+ data['comment'] = ''
+
+ if self.sign:
+ data['gpg_signature'] = (os.path.basename(filename) + ".asc",
+ open(filename+".asc", "rb").read())
+
+ # set up the authentication
+ user_pass = (self.username + ":" + self.password).encode('ascii')
+ # The exact encoding of the authentication string is debated.
+ # Anyway PyPI only accepts ascii for both username or password.
+ auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
+
+ # Build up the MIME payload for the POST data
+ boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ sep_boundary = b'\r\n--' + boundary.encode('ascii')
+ end_boundary = sep_boundary + b'--\r\n'
+ body = io.BytesIO()
+ for key, value in data.items():
+ title = '\r\nContent-Disposition: form-data; name="%s"' % key
+ # handle multiple entries for the same name
+ if not isinstance(value, list):
+ value = [value]
+ for value in value:
+ if type(value) is tuple:
+ title += '; filename="%s"' % value[0]
+ value = value[1]
+ else:
+ value = str(value).encode('utf-8')
+ body.write(sep_boundary)
+ body.write(title.encode('utf-8'))
+ body.write(b"\r\n\r\n")
+ body.write(value)
+ body.write(end_boundary)
+ body = body.getvalue()
+
+ msg = "Submitting %s to %s" % (filename, self.repository)
+ self.announce(msg, log.INFO)
+
+ # build the Request
+ headers = {
+ 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
+ 'Content-length': str(len(body)),
+ 'Authorization': auth,
+ }
+
+ request = Request(self.repository, data=body,
+ headers=headers)
+ # send the data
+ try:
+ result = urlopen(request)
+ status = result.getcode()
+ reason = result.msg
+ except HTTPError as e:
+ status = e.code
+ reason = e.msg
+ except OSError as e:
+ self.announce(str(e), log.ERROR)
+ raise
+
+ if status == 200:
+ self.announce('Server response (%s): %s' % (status, reason),
+ log.INFO)
+ if self.show_response:
+ text = getattr(self, '_read_pypi_response',
+ lambda x: None)(result)
+ if text is not None:
+ msg = '\n'.join(('-' * 75, text, '-' * 75))
+ self.announce(msg, log.INFO)
+ else:
+ msg = 'Upload failed (%s): %s' % (status, reason)
+ self.announce(msg, log.ERROR)
+ raise DistutilsError(msg)
+
def _load_password_from_keyring(self):
"""
Attempt to load password from keyring. Suppress Exceptions.
import io
import os
import sys
+
+import warnings
+import functools
from collections import defaultdict
from functools import partial
+from functools import wraps
from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
return configuration_to_dict(handlers)
+def _get_option(target_obj, key):
+ """
+ Given a target object and option key, get that option from
+ the target object, either through a get_{key} method or
+ from an attribute directly.
+ """
+ getter_name = 'get_{key}'.format(**locals())
+ by_attribute = functools.partial(getattr, target_obj, key)
+ getter = getattr(target_obj, getter_name, by_attribute)
+ return getter()
+
+
def configuration_to_dict(handlers):
"""Returns configuration data gathered by given handlers as a dict.
config_dict = defaultdict(dict)
for handler in handlers:
-
- obj_alias = handler.section_prefix
- target_obj = handler.target_obj
-
for option in handler.set_options:
- getter = getattr(target_obj, 'get_%s' % option, None)
-
- if getter is None:
- value = getattr(target_obj, option)
-
- else:
- value = getter()
-
- config_dict[obj_alias][option] = value
+ value = _get_option(handler.target_obj, option)
+ config_dict[handler.section_prefix][option] = value
return config_dict
options.parse()
meta = ConfigMetadataHandler(
- distribution.metadata, command_options, ignore_option_errors, distribution.package_dir)
+ distribution.metadata, command_options, ignore_option_errors,
+ distribution.package_dir)
meta.parse()
return meta, options
section_parser_method(section_options)
+ def _deprecated_config_handler(self, func, msg, warning_class):
+ """ this function will wrap around parameters that are deprecated
+
+ :param msg: deprecation message
+ :param warning_class: class of warning exception to be raised
+ :param func: function to be wrapped around
+ """
+ @wraps(func)
+ def config_handler(*args, **kwargs):
+ warnings.warn(msg, warning_class)
+ return func(*args, **kwargs)
+
+ return config_handler
+
class ConfigMetadataHandler(ConfigHandler):
'platforms': parse_list,
'keywords': parse_list,
'provides': parse_list,
- 'requires': parse_list,
+ 'requires': self._deprecated_config_handler(parse_list,
+ "The requires parameter is deprecated, please use " +
+ "install_requires for runtime dependencies.",
+ DeprecationWarning),
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': parse_file,
# Be strict about versions loaded from file because it's easy to
# accidentally include newlines and other unintended content
if isinstance(parse(version), LegacyVersion):
- raise DistutilsOptionError('Version loaded from %s does not comply with PEP 440: %s' % (
- value, version
- ))
+ tmpl = (
+ 'Version loaded from {value} does not '
+ 'comply with PEP 440: {version}'
+ )
+ raise DistutilsOptionError(tmpl.format(**locals()))
+
return version
version = self._parse_attr(value, self.package_dir)
find_directives = ['find:', 'find_namespace:']
trimmed_value = value.strip()
- if not trimmed_value in find_directives:
+ if trimmed_value not 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')
+ 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(
import distutils.cmd
import distutils.dist
import itertools
+
+
from collections import defaultdict
+from email import message_from_file
+
from distutils.errors import (
DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError,
)
from setuptools.extern import packaging
from setuptools.extern.six.moves import map, filter, filterfalse
+from . import SetuptoolsDeprecationWarning
+
from setuptools.depends import Require
from setuptools import windows_support
from setuptools.monkey import get_unpatched
def _get_unpatched(cls):
- warnings.warn("Do not call this function", DeprecationWarning)
+ warnings.warn("Do not call this function", DistDeprecationWarning)
return get_unpatched(cls)
-def get_metadata_version(dist_md):
- if dist_md.long_description_content_type or dist_md.provides_extras:
- return StrictVersion('2.1')
- elif (dist_md.maintainer is not None or
- dist_md.maintainer_email is not None or
- getattr(dist_md, 'python_requires', None) is not None):
- return StrictVersion('1.2')
- elif (dist_md.provides or dist_md.requires or dist_md.obsoletes or
- dist_md.classifiers or dist_md.download_url):
- return StrictVersion('1.1')
+def get_metadata_version(self):
+ mv = getattr(self, 'metadata_version', None)
+
+ if mv is None:
+ if self.long_description_content_type or self.provides_extras:
+ mv = StrictVersion('2.1')
+ elif (self.maintainer is not None or
+ self.maintainer_email is not None or
+ getattr(self, 'python_requires', None) is not None):
+ mv = StrictVersion('1.2')
+ elif (self.provides or self.requires or self.obsoletes or
+ self.classifiers or self.download_url):
+ mv = StrictVersion('1.1')
+ else:
+ mv = StrictVersion('1.0')
+
+ self.metadata_version = mv
+
+ return mv
+
+
+def read_pkg_file(self, file):
+ """Reads the metadata values from a file object."""
+ msg = message_from_file(file)
+
+ def _read_field(name):
+ value = msg[name]
+ if value == 'UNKNOWN':
+ return None
+ return value
+
+ def _read_list(name):
+ values = msg.get_all(name, None)
+ if values == []:
+ return None
+ return values
+
+ self.metadata_version = StrictVersion(msg['metadata-version'])
+ self.name = _read_field('name')
+ self.version = _read_field('version')
+ self.description = _read_field('summary')
+ # we are filling author only.
+ self.author = _read_field('author')
+ self.maintainer = None
+ self.author_email = _read_field('author-email')
+ self.maintainer_email = None
+ self.url = _read_field('home-page')
+ self.license = _read_field('license')
+
+ if 'download-url' in msg:
+ self.download_url = _read_field('download-url')
+ else:
+ self.download_url = None
+
+ self.long_description = _read_field('description')
+ self.description = _read_field('summary')
+
+ if 'keywords' in msg:
+ self.keywords = _read_field('keywords').split(',')
+
+ self.platforms = _read_list('platform')
+ self.classifiers = _read_list('classifier')
- return StrictVersion('1.0')
+ # PEP 314 - these fields only exist in 1.1
+ if self.metadata_version == StrictVersion('1.1'):
+ self.requires = _read_list('requires')
+ self.provides = _read_list('provides')
+ self.obsoletes = _read_list('obsoletes')
+ else:
+ self.requires = None
+ self.provides = None
+ self.obsoletes = None
# Based on Python 3.5 version
def write_pkg_file(self, file):
"""Write the PKG-INFO format data to a file object.
"""
- version = get_metadata_version(self)
+ version = self.get_metadata_version()
+
+ if six.PY2:
+ def write_field(key, value):
+ file.write("%s: %s\n" % (key, self._encode_field(value)))
+ else:
+ def write_field(key, value):
+ file.write("%s: %s\n" % (key, value))
+
- file.write('Metadata-Version: %s\n' % version)
- file.write('Name: %s\n' % self.get_name())
- file.write('Version: %s\n' % self.get_version())
- file.write('Summary: %s\n' % self.get_description())
- file.write('Home-page: %s\n' % self.get_url())
+ write_field('Metadata-Version', str(version))
+ write_field('Name', self.get_name())
+ write_field('Version', self.get_version())
+ write_field('Summary', self.get_description())
+ write_field('Home-page', self.get_url())
if version < StrictVersion('1.2'):
- file.write('Author: %s\n' % self.get_contact())
- file.write('Author-email: %s\n' % self.get_contact_email())
+ write_field('Author:', self.get_contact())
+ write_field('Author-email:', self.get_contact_email())
else:
optional_fields = (
('Author', 'author'),
for field, attr in optional_fields:
attr_val = getattr(self, attr)
- if six.PY2:
- attr_val = self._encode_field(attr_val)
if attr_val is not None:
- file.write('%s: %s\n' % (field, attr_val))
+ write_field(field, attr_val)
- file.write('License: %s\n' % self.get_license())
+ write_field('License', self.get_license())
if self.download_url:
- file.write('Download-URL: %s\n' % self.download_url)
+ write_field('Download-URL', self.download_url)
for project_url in self.project_urls.items():
- file.write('Project-URL: %s, %s\n' % project_url)
+ write_field('Project-URL', '%s, %s' % project_url)
long_desc = rfc822_escape(self.get_long_description())
- file.write('Description: %s\n' % long_desc)
+ write_field('Description', long_desc)
keywords = ','.join(self.get_keywords())
if keywords:
- file.write('Keywords: %s\n' % keywords)
+ write_field('Keywords', keywords)
if version >= StrictVersion('1.2'):
for platform in self.get_platforms():
- file.write('Platform: %s\n' % platform)
+ write_field('Platform', platform)
else:
self._write_list(file, 'Platform', self.get_platforms())
# Setuptools specific for PEP 345
if hasattr(self, 'python_requires'):
- file.write('Requires-Python: %s\n' % self.python_requires)
+ write_field('Requires-Python', self.python_requires)
# PEP 566
if self.long_description_content_type:
- file.write(
- 'Description-Content-Type: %s\n' %
+ write_field(
+ 'Description-Content-Type',
self.long_description_content_type
)
if self.provides_extras:
for extra in self.provides_extras:
- file.write('Provides-Extra: %s\n' % extra)
+ write_field('Provides-Extra', extra)
sequence = tuple, list
"Features are deprecated and will be removed in a future "
"version. See https://github.com/pypa/setuptools/issues/65."
)
- warnings.warn(msg, DeprecationWarning, stacklevel=3)
+ warnings.warn(msg, DistDeprecationWarning, stacklevel=3)
def __init__(
self, description, standard=False, available=True,
" doesn't contain any packages or modules under %s"
% (self.description, item, item)
)
+
+
+class DistDeprecationWarning(SetuptoolsDeprecationWarning):
+ """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning."""
warehouse = 'https://upload.pypi.org/legacy/'
distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
- _patch_distribution_metadata_write_pkg_file()
+ _patch_distribution_metadata()
# Install Distribution throughout the distutils
for module in distutils.dist, distutils.core, distutils.cmd:
patch_for_msvc_specialized_compiler()
-def _patch_distribution_metadata_write_pkg_file():
- """Patch write_pkg_file to also write Requires-Python/Requires-External"""
- distutils.dist.DistributionMetadata.write_pkg_file = (
- setuptools.dist.write_pkg_file
- )
+def _patch_distribution_metadata():
+ """Patch write_pkg_file and read_pkg_file for higher metadata standards"""
+ for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
+ new_val = getattr(setuptools.dist, attr)
+ setattr(distutils.dist.DistributionMetadata, attr, new_val)
def patch_func(replacement, target_mod, func_name):
import os
import shutil
+import tarfile
import pytest
+from setuptools.build_meta import build_sdist
from .files import build_files
from .textwrap import DALS
+from . import py2_only
__metaclass__ = type
-futures = pytest.importorskip('concurrent.futures')
-importlib = pytest.importorskip('importlib')
+# Backports on Python 2.7
+import importlib
+from concurrent import futures
class BuildBackendBase:
assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-@pytest.mark.skipif('sys.version_info > (3,)')
+@py2_only
def test_prepare_metadata_for_build_wheel_with_str(build_backend):
dist_dir = os.path.abspath(str('pip-dist-info'))
os.makedirs(dist_dir)
sdist_name = build_backend.build_sdist("out_sdist")
assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+
+def test_build_sdist_setup_py_exists(tmpdir_cwd):
+ # If build_sdist is called from a script other than setup.py,
+ # ensure setup.py is include
+ build_files(defns[0])
+ targz_path = build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert any('setup.py' in name for name in tar.getnames())
+
+
+def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
+ # Ensure that MANIFEST.in can exclude setup.py
+ files = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""),
+ 'hello.py': '',
+ 'MANIFEST.in': DALS("""
+ exclude setup.py
+ """)
+ }
+
+ build_files(files)
+ targz_path = build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert not any('setup.py' in name for name in tar.getnames())
+
with get_dist(tmpdir) as dist:
assert set(dist.metadata.classifiers) == expected
+ def test_deprecated_config_handlers(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'description = Some description\n'
+ 'requires = some, requirement\n'
+ )
+
+ with pytest.deprecated_call():
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.version == '10.1.1'
+ assert metadata.description == 'Some description'
+ assert metadata.requires == ['some', 'requirement']
+
class TestOptions:
from __future__ import unicode_literals
import io
-
+from setuptools.dist import DistDeprecationWarning, _get_unpatched
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
from .test_easy_install import make_nspkg_sdist
import pytest
+import six
def test_dist_fetch_build_egg(tmpdir):
assert [dist.key for dist in resolved_dists if dist] == reqs
+def test_dist__get_unpatched_deprecated():
+ pytest.warns(DistDeprecationWarning, _get_unpatched, [""])
+
+
+def __read_test_cases():
+ # Metadata version 1.0
+ base_attrs = {
+ "name": "package",
+ "version": "0.0.1",
+ "author": "Foo Bar",
+ "author_email": "foo@bar.net",
+ "long_description": "Long\ndescription",
+ "description": "Short description",
+ "keywords": ["one", "two"]
+ }
+
+ def merge_dicts(d1, d2):
+ d1 = d1.copy()
+ d1.update(d2)
+
+ return d1
+
+ test_cases = [
+ ('Metadata version 1.0', base_attrs.copy()),
+ ('Metadata version 1.1: Provides', merge_dicts(base_attrs, {
+ 'provides': ['package']
+ })),
+ ('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, {
+ 'obsoletes': ['foo']
+ })),
+ ('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, {
+ 'classifiers': [
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.7',
+ 'License :: OSI Approved :: MIT License'
+ ]})),
+ ('Metadata version 1.1: Download URL', merge_dicts(base_attrs, {
+ 'download_url': 'https://example.com'
+ })),
+ ('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, {
+ 'python_requires': '>=3.7'
+ })),
+ pytest.param('Metadata Version 1.2: Project-Url',
+ merge_dicts(base_attrs, {
+ 'project_urls': {
+ 'Foo': 'https://example.bar'
+ }
+ }), marks=pytest.mark.xfail(
+ reason="Issue #1578: project_urls not read"
+ )),
+ ('Metadata Version 2.1: Long Description Content Type',
+ merge_dicts(base_attrs, {
+ 'long_description_content_type': 'text/x-rst; charset=UTF-8'
+ })),
+ pytest.param('Metadata Version 2.1: Provides Extra',
+ merge_dicts(base_attrs, {
+ 'provides_extras': ['foo', 'bar']
+ }), marks=pytest.mark.xfail(reason="provides_extras not read")),
+ ]
+
+ return test_cases
+
+
+@pytest.mark.parametrize('name,attrs', __read_test_cases())
+def test_read_metadata(name, attrs):
+ dist = Distribution(attrs)
+ metadata_out = dist.metadata
+ dist_class = metadata_out.__class__
+
+ # Write to PKG_INFO and then load into a new metadata object
+ if six.PY2:
+ PKG_INFO = io.BytesIO()
+ else:
+ PKG_INFO = io.StringIO()
+
+ metadata_out.write_pkg_file(PKG_INFO)
+
+ PKG_INFO.seek(0)
+ metadata_in = dist_class()
+ metadata_in.read_pkg_file(PKG_INFO)
+
+ tested_attrs = [
+ ('name', dist_class.get_name),
+ ('version', dist_class.get_version),
+ ('metadata_version', dist_class.get_metadata_version),
+ ('provides', dist_class.get_provides),
+ ('description', dist_class.get_description),
+ ('download_url', dist_class.get_download_url),
+ ('keywords', dist_class.get_keywords),
+ ('platforms', dist_class.get_platforms),
+ ('obsoletes', dist_class.get_obsoletes),
+ ('requires', dist_class.get_requires),
+ ('classifiers', dist_class.get_classifiers),
+ ('project_urls', lambda s: getattr(s, 'project_urls', {})),
+ ('provides_extras', lambda s: getattr(s, 'provides_extras', set())),
+ ]
+
+ for attr, getter in tested_attrs:
+ assert getter(metadata_in) == getter(metadata_out)
+
+
def __maintainer_test_cases():
attrs = {"name": "package",
"version": "1.0",
import io
import zipfile
import mock
-
+from setuptools.command.easy_install import EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter
import time
from setuptools.extern import six
from setuptools.extern.six.moves import urllib
assert (target / 'mypkg_script').exists()
+ def test_dist_get_script_args_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_script_args(None, None)
+
+ def test_dist_get_script_header_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_script_header("")
+
+ def test_dist_get_writer_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ ScriptWriter.get_writer(None)
+
+ def test_dist_WindowsScriptWriter_get_writer_deprecated(self):
+ with pytest.warns(EasyInstallDeprecationWarning):
+ WindowsScriptWriter.get_writer()
+
@pytest.mark.filterwarnings('ignore:Unbuilt egg')
class TestPTHFileWriter:
def test_add_from_cwd_site_sets_dirty(self):
import stat
import time
-from setuptools.command.egg_info import egg_info, manifest_maker
+from setuptools.command.egg_info import egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision
from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
for msg in fixtures:
assert manifest_maker._should_suppress_warning(msg)
+ def test_egg_info_includes_setup_py(self, tmpdir_cwd):
+ self._create_project()
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.script_name = "non_setup.py"
+ egg_info_instance = egg_info(dist)
+ egg_info_instance.finalize_options()
+ egg_info_instance.run()
+
+ assert 'setup.py' in egg_info_instance.filelist.files
+
+ with open(egg_info_instance.egg_info + "/SOURCES.txt") as f:
+ sources = f.read().split('\n')
+ assert 'setup.py' in sources
+
def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
pkg_info_lines = pkginfo_file.read().split('\n')
assert 'Version: 0.0.0.dev0' in pkg_info_lines
+
+ def test_get_pkg_info_revision_deprecated(self):
+ pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
from setuptools.dist import Distribution
from setuptools.extern import six
from setuptools.tests.textwrap import DALS
+from . import py3_only
import pytest
__metaclass__ = type
-py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
-
def make_local_path(s):
"""Converts '/' in a string to os.sep"""
class TestNamespaces:
- @pytest.mark.xfail(
+ @pytest.mark.skipif(
sys.version_info < (3, 5),
reason="Requires importlib.util.module_from_spec",
)
from setuptools.dist import Distribution
from setuptools.tests import fail_on_ascii
from .text import Filenames
+from . import py3_only
-py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
SETUP_ATTRS = {
'name': 'sdist_test',
class TestSdistTest:
def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp()
- f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
- f.write(SETUP_PY)
- f.close()
+ with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f:
+ f.write(SETUP_PY)
# Set up the rest of the test package
test_pkg = os.path.join(self.temp_dir, 'sdist_test')
assert os.path.join('sdist_test', 'c.rst') not in manifest
assert os.path.join('d', 'e.dat') in manifest
+ def test_setup_py_exists(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' in manifest
+
+ def test_setup_py_missing(self):
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ if os.path.exists("setup.py"):
+ os.remove("setup.py")
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
+ def test_setup_py_excluded(self):
+ with open("MANIFEST.in", "w") as manifest_file:
+ manifest_file.write("exclude setup.py")
+
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'foo.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ manifest = cmd.filelist.files
+ assert 'setup.py' not in manifest
+
def test_defaults_case_sensitivity(self):
"""
Make sure default files (README.*, etc.) are added in a case-sensitive
assert out == 'Foo\n'
-@pytest.mark.xfail(
- sys.version_info < (2, 7),
- reason="No discover support for unittest on Python 2.6",
-)
@pytest.mark.usefixtures('tmpdir_cwd', 'quiet_log')
def test_tests_are_run_once(capfd):
params = dict(
import mock
+import os
+import re
+
from distutils import log
+from distutils.errors import DistutilsError
+from distutils.version import StrictVersion
import pytest
+import six
from setuptools.command.upload import upload
from setuptools.dist import Distribution
+def _parse_upload_body(body):
+ boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ entries = []
+ name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"')
+
+ for entry in body.split(boundary):
+ pair = entry.split(u'\r\n\r\n')
+ if not len(pair) == 2:
+ continue
+
+ key, value = map(six.text_type.strip, pair)
+ m = name_re.match(key)
+ if m is not None:
+ key = m.group(1)
+
+ entries.append((key, value))
+
+ return entries
+
+
+@pytest.fixture
+def patched_upload(tmpdir):
+ class Fix:
+ def __init__(self, cmd, urlopen):
+ self.cmd = cmd
+ self.urlopen = urlopen
+
+ def __iter__(self):
+ return iter((self.cmd, self.urlopen))
+
+ def get_uploaded_metadata(self):
+ request = self.urlopen.call_args_list[0][0][0]
+ body = request.data.decode('utf-8')
+ entries = dict(_parse_upload_body(body))
+
+ return entries
+
+ class ResponseMock(mock.Mock):
+ def getheader(self, name, default=None):
+ """Mocked getheader method for response object"""
+ return {
+ 'content-type': 'text/plain; charset=utf-8',
+ }.get(name.lower(), default)
+
+ with mock.patch('setuptools.command.upload.urlopen') as urlopen:
+ urlopen.return_value = ResponseMock()
+ urlopen.return_value.getcode.return_value = 200
+ urlopen.return_value.read.return_value = b''
+
+ content = os.path.join(str(tmpdir), "content_data")
+
+ with open(content, 'w') as f:
+ f.write("Some content")
+
+ dist = Distribution()
+ dist.dist_files = [('sdist', '3.7.0', content)]
+
+ cmd = upload(dist)
+ cmd.announce = mock.Mock()
+ cmd.username = 'user'
+ cmd.password = 'hunter2'
+
+ yield Fix(cmd, urlopen)
+
+
class TestUploadTest:
+ def test_upload_metadata(self, patched_upload):
+ cmd, patch = patched_upload
+
+ # Set the metadata version to 2.1
+ cmd.distribution.metadata.metadata_version = '2.1'
+
+ # Run the command
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # Make sure we did the upload
+ patch.assert_called_once()
+
+ # Make sure the metadata version is correct in the headers
+ entries = patched_upload.get_uploaded_metadata()
+ assert entries['metadata_version'] == '2.1'
+
def test_warns_deprecation(self):
dist = Distribution()
dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
"upload instead (https://pypi.org/p/twine/)",
log.WARN
)
+
+ @pytest.mark.parametrize('url', [
+ 'https://example.com/a;parameter', # Has parameters
+ 'https://example.com/a?query', # Has query
+ 'https://example.com/a#fragment', # Has fragment
+ 'ftp://example.com', # Invalid scheme
+
+ ])
+ def test_upload_file_invalid_url(self, url, patched_upload):
+ patched_upload.urlopen.side_effect = Exception("Should not be reached")
+
+ cmd = patched_upload.cmd
+ cmd.repository = url
+
+ cmd.ensure_finalized()
+ with pytest.raises(AssertionError):
+ cmd.run()
+
+ def test_upload_file_http_error(self, patched_upload):
+ patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError(
+ 'https://example.com',
+ 404,
+ 'File not found',
+ None,
+ None
+ )
+
+ cmd = patched_upload.cmd
+ cmd.ensure_finalized()
+
+ with pytest.raises(DistutilsError):
+ cmd.run()
+
+ cmd.announce.assert_any_call(
+ 'Upload failed (404): File not found',
+ log.ERROR)
+
+ def test_upload_file_os_error(self, patched_upload):
+ patched_upload.urlopen.side_effect = OSError("Invalid")
+
+ cmd = patched_upload.cmd
+ cmd.ensure_finalized()
+
+ with pytest.raises(OSError):
+ cmd.run()
+
+ cmd.announce.assert_any_call('Invalid', log.ERROR)
+
+ @mock.patch('setuptools.command.upload.spawn')
+ def test_upload_file_gpg(self, spawn, patched_upload):
+ cmd, urlopen = patched_upload
+
+ cmd.sign = True
+ cmd.identity = "Alice"
+ cmd.dry_run = True
+ content_fname = cmd.distribution.dist_files[0][2]
+ signed_file = content_fname + '.asc'
+
+ with open(signed_file, 'wb') as f:
+ f.write("signed-data".encode('utf-8'))
+
+ cmd.ensure_finalized()
+ cmd.run()
+
+ # Make sure that GPG was called
+ spawn.assert_called_once_with([
+ "gpg", "--detach-sign", "--local-user", "Alice", "-a",
+ content_fname
+ ], dry_run=True)
+
+ # Read the 'signed' data that was transmitted
+ entries = patched_upload.get_uploaded_metadata()
+ assert entries['gpg_signature'] == 'signed-data'
+
+ def test_show_response_no_error(self, patched_upload):
+ # This test is just that show_response doesn't throw an error
+ # It is not really important what the printed response looks like
+ # in a deprecated command, but we don't want to introduce new
+ # errors when importing this function from distutils
+
+ patched_upload.cmd.show_response = True
+ patched_upload.cmd.ensure_finalized()
+ patched_upload.cmd.run()
Check pip can upgrade setuptools from source.
"""
dist_dir = virtualenv.workspace
- if sys.version_info < (2, 7):
- # Python 2.6 support was dropped in wheel 0.30.0.
- virtualenv.run('pip install -U "wheel<0.30.0"')
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
'cd {source}',
coverage>=4.5.1
pytest-cov>=2.5.1
paver; python_version>="3.6"
+futures; python_version=="2.7"