aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2019-10-26 16:36:07 -0400
committerJason R. Coombs <jaraco@jaraco.com>2019-10-26 16:36:07 -0400
commitc11270aa6bffd8913c6e074f09b9d920c8f19002 (patch)
tree5c97e2e05dcabb9b13e954528fa6783740f68fb6
parent6a3674a18d22dbea4d6b513b8e8a7ac28ada5eba (diff)
parent297f2adceda3af402fc08311e42505c8cdc9c54b (diff)
downloadexternal_python_setuptools-c11270aa6bffd8913c6e074f09b9d920c8f19002.tar.gz
external_python_setuptools-c11270aa6bffd8913c6e074f09b9d920c8f19002.tar.bz2
external_python_setuptools-c11270aa6bffd8913c6e074f09b9d920c8f19002.zip
Merge branch 'master' into fix/1700
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.travis.yml10
-rw-r--r--CHANGES.rst34
-rw-r--r--changelog.d/1697.change.rst1
-rw-r--r--changelog.d/1749.change.rst1
-rw-r--r--changelog.d/1750.change.rst1
-rw-r--r--changelog.d/1860.doc.rst1
-rw-r--r--changelog.d/1878.change.rst1
-rw-r--r--changelog.d/1884.doc.rst1
-rw-r--r--docs/conf.py2
-rw-r--r--docs/developer-guide.txt14
-rw-r--r--docs/keywords.txt6
-rw-r--r--docs/setuptools.txt29
-rw-r--r--launcher.c1
-rw-r--r--pkg_resources/__init__.py13
-rw-r--r--pkg_resources/tests/test_pkg_resources.py56
-rw-r--r--pytest.ini2
-rw-r--r--setup.cfg3
-rw-r--r--setuptools/_vendor/ordered_set.py488
-rw-r--r--setuptools/_vendor/vendored.txt1
-rw-r--r--setuptools/command/build_ext.py10
-rw-r--r--setuptools/command/easy_install.py3
-rw-r--r--setuptools/command/install_lib.py6
-rw-r--r--setuptools/command/test.py10
-rw-r--r--setuptools/config.py2
-rw-r--r--setuptools/dist.py37
-rw-r--r--setuptools/extern/__init__.py2
-rw-r--r--setuptools/py33compat.py6
-rw-r--r--setuptools/tests/test_config.py36
-rw-r--r--setuptools/tests/test_dist.py67
-rw-r--r--setuptools/tests/test_egg_info.py1
-rw-r--r--setuptools/tests/test_integration.py1
-rw-r--r--setuptools/tests/test_test.py60
-rw-r--r--setuptools/tests/test_virtualenv.py7
-rw-r--r--tests/manual_test.py100
-rw-r--r--tests/requirements.txt3
36 files changed, 859 insertions, 159 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 87acb5ef..d6768cb8 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 41.0.1
+current_version = 41.4.0
commit = True
tag = True
diff --git a/.travis.yml b/.travis.yml
index ffcad998..8b7cece8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,9 +8,9 @@ jobs:
python: 2.7
- <<: *latest_py2
env: LANG=C
- - python: pypy2.7-6.0.0
+ - python: pypy
env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow).
- - python: pypy3.5-6.0.0
+ - python: pypy3
env: DISABLE_COVERAGE=1
- python: 3.4
- python: 3.5
@@ -21,6 +21,8 @@ jobs:
- <<: *latest_py3
env: LANG=C
- python: 3.8-dev
+ - <<: *latest_py3
+ env: TOXENV=docs DISABLE_COVERAGE=1
- <<: *default_py
stage: deploy (to PyPI for tagged commits)
if: tag IS present
@@ -35,9 +37,9 @@ jobs:
on:
tags: true
all_branches: true
- user: jaraco
+ user: __token__
password:
- secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g=
+ secure: FSp9KU+pdvWPxBOaxe6BNmcJ9y8259G3/NdTJ00r0qx/xMLpSneGjpuLqoD6BL2JoM6gRwurwakWoH/9Ah+Di7afETjMnL6WJKtDZ+Uu3YLx3ss7/FlhVz6zmVTaDJUzuo9dGr//qLBQTIxVjGYfQelRJyfMAXtrYWdeT/4489E45lMw+86Z/vnSBOxs4lWekeQW5Gem0cDViWu67RRiGkAEvrYVwuImMr2Dyhpv+l/mQGQIS/ezXuAEFToE6+q8VUVe/aK498Qovdc+O4M7OYk1JouFpffZ3tVZ6iWHQFcR11480UdI6VCIcFpPvGC/J8MWUWLjq7YOm0X9jPXgdYMUQLAP4clFgUr2qNoRSKWfuQlNdVVuS2htYcjJ3eEl90FhcIZKp+WVMrypRPOQJ8CBielZEs0dhytRrZSaJC1BNq25O/BPzws8dL8hYtoXsM6I3Zv5cZgdyqyq/eOEMCX7Cetv6do0U41VGEV5UohvyyuwH5l9GCuPREpY3sXayPg8fw7XcPjvvzSVyjcUT/ePW8sfnAyWZnngjweAn6dK8IFGPuSPQdlos78uxeUOvCVUW0xv/0m4lX73yoHdVVdLbu1MJTyibFGec86Bew9JqIcDlhHaIJ9ihZ9Z9tOtvp1cuNyKYE4kvmOtumDDicEw4DseYn2z5sZDTYTBsKY=
distributions: release
skip_cleanup: true
skip_upload_docs: true
diff --git a/CHANGES.rst b/CHANGES.rst
index 9da22537..ecde25a5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,37 @@
+v41.4.0
+-------
+
+* #1847: In declarative config, now traps errors when invalid ``python_requires`` values are supplied.
+
+
+v41.3.0
+-------
+
+* #1690: When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+.
+* #1858: Fixed failing integration test triggered by 'long_description_content_type' in packaging.
+
+
+v41.2.0
+-------
+
+* #479: Remove some usage of the deprecated ``imp`` module.
+* #1565: Changed html_sidebars from string to list of string as per
+ https://www.sphinx-doc.org/en/master/changes.html#id58
+
+
+v41.1.0
+-------
+
+* #1697: Moved most of the constants from setup.py to setup.cfg
+* #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory.
+* #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist.
+* #1756: Force metadata-version >= 1.2. when project urls are present.
+* #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings.
+* #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9.
+* #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file.
+* #1776: Use license classifiers rather than the license field.
+
+
v41.0.1
-------
diff --git a/changelog.d/1697.change.rst b/changelog.d/1697.change.rst
deleted file mode 100644
index 44818b3c..00000000
--- a/changelog.d/1697.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-Moved most of the constants from setup.py to setup.cfg
diff --git a/changelog.d/1749.change.rst b/changelog.d/1749.change.rst
deleted file mode 100644
index de678072..00000000
--- a/changelog.d/1749.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory.
diff --git a/changelog.d/1750.change.rst b/changelog.d/1750.change.rst
deleted file mode 100644
index 7a22229e..00000000
--- a/changelog.d/1750.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist.
diff --git a/changelog.d/1860.doc.rst b/changelog.d/1860.doc.rst
new file mode 100644
index 00000000..f3554643
--- /dev/null
+++ b/changelog.d/1860.doc.rst
@@ -0,0 +1 @@
+Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0.
diff --git a/changelog.d/1878.change.rst b/changelog.d/1878.change.rst
new file mode 100644
index 00000000..0774b5d3
--- /dev/null
+++ b/changelog.d/1878.change.rst
@@ -0,0 +1 @@
+Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``.
diff --git a/changelog.d/1884.doc.rst b/changelog.d/1884.doc.rst
new file mode 100644
index 00000000..45615d5d
--- /dev/null
+++ b/changelog.d/1884.doc.rst
@@ -0,0 +1 @@
+Added a trove classifier to document support for Python 3.8.
diff --git a/docs/conf.py b/docs/conf.py
index c7eb6d3f..cbd19fb4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -69,7 +69,7 @@ html_theme_path = ['_theme']
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-html_sidebars = {'index': 'indexsidebar.html'}
+html_sidebars = {'index': ['relations.html', 'sourcelink.html', 'indexsidebar.html', 'searchbox.html']}
# If false, no module index is generated.
html_use_modindex = False
diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt
index a5942c8b..d145fba1 100644
--- a/docs/developer-guide.txt
+++ b/docs/developer-guide.txt
@@ -137,3 +137,17 @@ To build the docs locally, use tox::
.. _Sphinx: http://www.sphinx-doc.org/en/master/
.. _published documentation: https://setuptools.readthedocs.io/en/latest/
+
+---------------------
+Vendored Dependencies
+---------------------
+
+Setuptools has some dependencies, but due to `bootstrapping issues
+<https://github.com/pypa/setuptools/issues/980>`, those dependencies
+cannot be declared as they won't be resolved soon enough to build
+setuptools from source. Eventually, this limitation may be lifted as
+PEP 517/518 reach ubiquitous adoption, but for now, Setuptools
+cannot declare dependencies other than through
+``setuptools/_vendor/vendored.txt`` and
+``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of
+``paver update_vendored`` (pavement.py).
diff --git a/docs/keywords.txt b/docs/keywords.txt
index d56014e0..56356190 100644
--- a/docs/keywords.txt
+++ b/docs/keywords.txt
@@ -259,6 +259,8 @@
specified test suite, e.g. via ``setup.py test``. See the section on the
:ref:`test` command below for more details.
+ New in 41.5.0: Deprecated the test command.
+
``tests_require``
If your project's tests need one or more additional packages besides those
needed to install it, you can use this option to specify them. It should
@@ -270,6 +272,8 @@
are run, but only downloaded to the project's setup directory if they're
not already installed locally.
+ New in 41.5.0: Deprecated the test command.
+
.. _test_loader:
``test_loader``
@@ -293,6 +297,8 @@
as long as you use the ``tests_require`` option to ensure that the package
containing the loader class is available when the ``test`` command is run.
+ New in 41.5.0: Deprecated the test command.
+
``eager_resources``
A list of strings naming resources that should be extracted together, if
any of them is needed, or if any C extensions included in the project are
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index ae85b8f5..ce022ac1 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -136,16 +136,18 @@ dependencies, and perhaps some data files and scripts::
author="Me",
author_email="me@example.com",
description="This is an Example Package",
- license="PSF",
keywords="hello world example examples",
url="http://example.com/HelloWorld/", # project home page, if any
project_urls={
"Bug Tracker": "https://bugs.example.com/HelloWorld/",
"Documentation": "https://docs.example.com/HelloWorld/",
"Source Code": "https://code.example.com/HelloWorld/",
- }
+ },
+ classifiers=[
+ 'License :: OSI Approved :: Python Software Foundation License'
+ ]
- # could also include long_description, download_url, classifiers, etc.
+ # could also include long_description, download_url, etc.
)
In the sections that follow, we'll explain what most of these ``setup()``
@@ -242,9 +244,9 @@ pre- or post-release tags. See the following sections for more details:
New and Changed ``setup()`` Keywords
====================================
-The following keyword arguments to ``setup()`` are supported by ``setuptools``.
-Some of them are optional; you do not have to supply them unless you need the
-associated ``setuptools`` feature.
+The following keyword arguments to ``setup()`` are added or changed by
+``setuptools``. All of them are optional; you do not have to supply them
+unless you need the associated ``setuptools`` feature.
.. include:: keywords.txt
@@ -513,6 +515,10 @@ using ``setup.py develop``.)
Dependencies that aren't in PyPI
--------------------------------
+.. warning::
+ Dependency links support has been dropped by pip starting with version
+ 19.0 (released 2019-01-22).
+
If your project depends on packages that don't exist on PyPI, you may still be
able to depend on them, as long as they are available for download as:
@@ -1532,6 +1538,9 @@ file locations.
``bdist_egg`` - Create a Python Egg for the project
===================================================
+.. warning::
+ **eggs** are deprecated in favor of wheels, and not supported by pip.
+
This command generates a Python Egg (``.egg`` file) for the project. Python
Eggs are the preferred binary distribution format for EasyInstall, because they
are cross-platform (for "pure" packages), directly importable, and contain
@@ -1978,6 +1987,11 @@ distutils configuration file the option will be added to (or removed from).
``test`` - Build package and run a unittest suite
=================================================
+.. warning::
+ ``test`` is deprecated and will be removed in a future version. Users
+ looking for a generic test entry point independent of test runner are
+ encouraged to use `tox <https://tox.readthedocs.io>`_.
+
When doing test-driven development, or running automated builds that need
testing before they are deployed for downloading or use, it's often useful
to be able to run a project's unit tests without actually deploying the project
@@ -2023,6 +2037,8 @@ available:
If you did not set a ``test_suite`` in your ``setup()`` call, and do not
provide a ``--test-suite`` option, an error will occur.
+New in 41.5.0: Deprecated the test command.
+
.. _upload:
@@ -2079,6 +2095,7 @@ boilerplate code in some cases.
license = BSD 3-Clause License
classifiers =
Framework :: Django
+ License :: OSI Approved :: BSD License
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
diff --git a/launcher.c b/launcher.c
index be69f0c6..23ef3ac2 100644
--- a/launcher.c
+++ b/launcher.c
@@ -37,6 +37,7 @@
#include <windows.h>
#include <tchar.h>
#include <fcntl.h>
+#include <process.h>
int child_pid=0;
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 97e08d68..1f170cfd 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -1416,8 +1416,17 @@ class NullProvider:
def get_metadata(self, name):
if not self.egg_info:
return ""
- value = self._get(self._fn(self.egg_info, name))
- return value.decode('utf-8') if six.PY3 else value
+ path = self._get_metadata_path(name)
+ value = self._get(path)
+ if six.PY2:
+ return value
+ try:
+ return value.decode('utf-8')
+ except UnicodeDecodeError as exc:
+ # Include the path in the error message to simplify
+ # troubleshooting, and without changing the exception type.
+ exc.reason += ' in {} file at path: {}'.format(name, path)
+ raise
def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name))
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
index fb77c685..5960868a 100644
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -18,6 +18,7 @@ except ImportError:
import mock
from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
+from setuptools.extern import six
from pkg_resources.extern.six.moves import map
from pkg_resources.extern.six import text_type, string_types
@@ -191,6 +192,59 @@ class TestResourceManager:
subprocess.check_call(cmd)
+def make_test_distribution(metadata_path, metadata):
+ """
+ Make a test Distribution object, and return it.
+
+ :param metadata_path: the path to the metadata file that should be
+ created. This should be inside a distribution directory that should
+ also be created. For example, an argument value might end with
+ "<project>.dist-info/METADATA".
+ :param metadata: the desired contents of the metadata file, as bytes.
+ """
+ dist_dir = os.path.dirname(metadata_path)
+ os.mkdir(dist_dir)
+ with open(metadata_path, 'wb') as f:
+ f.write(metadata)
+ dists = list(pkg_resources.distributions_from_metadata(dist_dir))
+ dist, = dists
+
+ return dist
+
+
+def test_get_metadata__bad_utf8(tmpdir):
+ """
+ Test a metadata file with bytes that can't be decoded as utf-8.
+ """
+ filename = 'METADATA'
+ # Convert the tmpdir LocalPath object to a string before joining.
+ metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename)
+ # Encode a non-ascii string with the wrong encoding (not utf-8).
+ metadata = 'née'.encode('iso-8859-1')
+ dist = make_test_distribution(metadata_path, metadata=metadata)
+
+ if six.PY2:
+ # In Python 2, get_metadata() doesn't do any decoding.
+ actual = dist.get_metadata(filename)
+ assert actual == metadata
+ return
+
+ # Otherwise, we are in the Python 3 case.
+ with pytest.raises(UnicodeDecodeError) as excinfo:
+ dist.get_metadata(filename)
+
+ exc = excinfo.value
+ actual = str(exc)
+ expected = (
+ # The error message starts with "'utf-8' codec ..." However, the
+ # spelling of "utf-8" can vary (e.g. "utf8") so we don't include it
+ "codec can't decode byte 0xe9 in position 1: "
+ 'invalid continuation byte in METADATA file at path: '
+ )
+ assert expected in actual, 'actual: {}'.format(actual)
+ assert actual.endswith(metadata_path), 'actual: {}'.format(actual)
+
+
# TODO: remove this in favor of Path.touch() when Python 2 is dropped.
def touch_file(path):
"""
@@ -242,7 +296,7 @@ def test_distribution_version_missing(tmpdir, suffix, expected_filename,
with pytest.raises(ValueError) as excinfo:
dist.version
- err = str(excinfo)
+ err = str(excinfo.value)
# Include a string expression after the assert so the full strings
# will be visible for inspection on failure.
assert expected_text in err, str((expected_text, err))
diff --git a/pytest.ini b/pytest.ini
index 1c5b6b09..612fb91f 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,5 +1,5 @@
[pytest]
-addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX
+addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX
norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .*
flake8-ignore =
setuptools/site-patch.py F821
diff --git a/setup.cfg b/setup.cfg
index ff87464b..373ae8b5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -19,7 +19,7 @@ universal = 1
[metadata]
name = setuptools
-version = 41.0.1
+version = 41.4.0
description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority
author_email = distutils-sig@python.org
@@ -42,6 +42,7 @@ classifiers =
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
diff --git a/setuptools/_vendor/ordered_set.py b/setuptools/_vendor/ordered_set.py
new file mode 100644
index 00000000..14876000
--- /dev/null
+++ b/setuptools/_vendor/ordered_set.py
@@ -0,0 +1,488 @@
+"""
+An OrderedSet is a custom MutableSet that remembers its order, so that every
+entry has an index that can be looked up.
+
+Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger,
+and released under the MIT license.
+"""
+import itertools as it
+from collections import deque
+
+try:
+ # Python 3
+ from collections.abc import MutableSet, Sequence
+except ImportError:
+ # Python 2.7
+ from collections import MutableSet, Sequence
+
+SLICE_ALL = slice(None)
+__version__ = "3.1"
+
+
+def is_iterable(obj):
+ """
+ Are we being asked to look up a list of things, instead of a single thing?
+ We check for the `__iter__` attribute so that this can cover types that
+ don't have to be known by this module, such as NumPy arrays.
+
+ Strings, however, should be considered as atomic values to look up, not
+ iterables. The same goes for tuples, since they are immutable and therefore
+ valid entries.
+
+ We don't need to check for the Python 2 `unicode` type, because it doesn't
+ have an `__iter__` attribute anyway.
+ """
+ return (
+ hasattr(obj, "__iter__")
+ and not isinstance(obj, str)
+ and not isinstance(obj, tuple)
+ )
+
+
+class OrderedSet(MutableSet, Sequence):
+ """
+ An OrderedSet is a custom MutableSet that remembers its order, so that
+ every entry has an index that can be looked up.
+
+ Example:
+ >>> OrderedSet([1, 1, 2, 3, 2])
+ OrderedSet([1, 2, 3])
+ """
+
+ def __init__(self, iterable=None):
+ self.items = []
+ self.map = {}
+ if iterable is not None:
+ self |= iterable
+
+ def __len__(self):
+ """
+ Returns the number of unique elements in the ordered set
+
+ Example:
+ >>> len(OrderedSet([]))
+ 0
+ >>> len(OrderedSet([1, 2]))
+ 2
+ """
+ return len(self.items)
+
+ def __getitem__(self, index):
+ """
+ Get the item at a given index.
+
+ If `index` is a slice, you will get back that slice of items, as a
+ new OrderedSet.
+
+ If `index` is a list or a similar iterable, you'll get a list of
+ items corresponding to those indices. This is similar to NumPy's
+ "fancy indexing". The result is not an OrderedSet because you may ask
+ for duplicate indices, and the number of elements returned should be
+ the number of elements asked for.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset[1]
+ 2
+ """
+ if isinstance(index, slice) and index == SLICE_ALL:
+ return self.copy()
+ elif is_iterable(index):
+ return [self.items[i] for i in index]
+ elif hasattr(index, "__index__") or isinstance(index, slice):
+ result = self.items[index]
+ if isinstance(result, list):
+ return self.__class__(result)
+ else:
+ return result
+ else:
+ raise TypeError("Don't know how to index an OrderedSet by %r" % index)
+
+ def copy(self):
+ """
+ Return a shallow copy of this object.
+
+ Example:
+ >>> this = OrderedSet([1, 2, 3])
+ >>> other = this.copy()
+ >>> this == other
+ True
+ >>> this is other
+ False
+ """
+ return self.__class__(self)
+
+ def __getstate__(self):
+ if len(self) == 0:
+ # The state can't be an empty list.
+ # We need to return a truthy value, or else __setstate__ won't be run.
+ #
+ # This could have been done more gracefully by always putting the state
+ # in a tuple, but this way is backwards- and forwards- compatible with
+ # previous versions of OrderedSet.
+ return (None,)
+ else:
+ return list(self)
+
+ def __setstate__(self, state):
+ if state == (None,):
+ self.__init__([])
+ else:
+ self.__init__(state)
+
+ def __contains__(self, key):
+ """
+ Test if the item is in this ordered set
+
+ Example:
+ >>> 1 in OrderedSet([1, 3, 2])
+ True
+ >>> 5 in OrderedSet([1, 3, 2])
+ False
+ """
+ return key in self.map
+
+ def add(self, key):
+ """
+ Add `key` as an item to this OrderedSet, then return its index.
+
+ If `key` is already in the OrderedSet, return the index it already
+ had.
+
+ Example:
+ >>> oset = OrderedSet()
+ >>> oset.append(3)
+ 0
+ >>> print(oset)
+ OrderedSet([3])
+ """
+ if key not in self.map:
+ self.map[key] = len(self.items)
+ self.items.append(key)
+ return self.map[key]
+
+ append = add
+
+ def update(self, sequence):
+ """
+ Update the set with the given iterable sequence, then return the index
+ of the last element inserted.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.update([3, 1, 5, 1, 4])
+ 4
+ >>> print(oset)
+ OrderedSet([1, 2, 3, 5, 4])
+ """
+ item_index = None
+ try:
+ for item in sequence:
+ item_index = self.add(item)
+ except TypeError:
+ raise ValueError(
+ "Argument needs to be an iterable, got %s" % type(sequence)
+ )
+ return item_index
+
+ def index(self, key):
+ """
+ Get the index of a given entry, raising an IndexError if it's not
+ present.
+
+ `key` can be an iterable of entries that is not a string, in which case
+ this returns a list of indices.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.index(2)
+ 1
+ """
+ if is_iterable(key):
+ return [self.index(subkey) for subkey in key]
+ return self.map[key]
+
+ # Provide some compatibility with pd.Index
+ get_loc = index
+ get_indexer = index
+
+ def pop(self):
+ """
+ Remove and return the last element from the set.
+
+ Raises KeyError if the set is empty.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.pop()
+ 3
+ """
+ if not self.items:
+ raise KeyError("Set is empty")
+
+ elem = self.items[-1]
+ del self.items[-1]
+ del self.map[elem]
+ return elem
+
+ def discard(self, key):
+ """
+ Remove an element. Do not raise an exception if absent.
+
+ The MutableSet mixin uses this to implement the .remove() method, which
+ *does* raise an error when asked to remove a non-existent item.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.discard(2)
+ >>> print(oset)
+ OrderedSet([1, 3])
+ >>> oset.discard(2)
+ >>> print(oset)
+ OrderedSet([1, 3])
+ """
+ if key in self:
+ i = self.map[key]
+ del self.items[i]
+ del self.map[key]
+ for k, v in self.map.items():
+ if v >= i:
+ self.map[k] = v - 1
+
+ def clear(self):
+ """
+ Remove all items from this OrderedSet.
+ """
+ del self.items[:]
+ self.map.clear()
+
+ def __iter__(self):
+ """
+ Example:
+ >>> list(iter(OrderedSet([1, 2, 3])))
+ [1, 2, 3]
+ """
+ return iter(self.items)
+
+ def __reversed__(self):
+ """
+ Example:
+ >>> list(reversed(OrderedSet([1, 2, 3])))
+ [3, 2, 1]
+ """
+ return reversed(self.items)
+
+ def __repr__(self):
+ if not self:
+ return "%s()" % (self.__class__.__name__,)
+ return "%s(%r)" % (self.__class__.__name__, list(self))
+
+ def __eq__(self, other):
+ """
+ Returns true if the containers have the same items. If `other` is a
+ Sequence, then order is checked, otherwise it is ignored.
+
+ Example:
+ >>> oset = OrderedSet([1, 3, 2])
+ >>> oset == [1, 3, 2]
+ True
+ >>> oset == [1, 2, 3]
+ False
+ >>> oset == [2, 3]
+ False
+ >>> oset == OrderedSet([3, 2, 1])
+ False
+ """
+ # In Python 2 deque is not a Sequence, so treat it as one for
+ # consistent behavior with Python 3.
+ if isinstance(other, (Sequence, deque)):
+ # Check that this OrderedSet contains the same elements, in the
+ # same order, as the other object.
+ return list(self) == list(other)
+ try:
+ other_as_set = set(other)
+ except TypeError:
+ # If `other` can't be converted into a set, it's not equal.
+ return False
+ else:
+ return set(self) == other_as_set
+
+ def union(self, *sets):
+ """
+ Combines all unique items.
+ Each items order is defined by its first appearance.
+
+ Example:
+ >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0])
+ >>> print(oset)
+ OrderedSet([3, 1, 4, 5, 2, 0])
+ >>> oset.union([8, 9])
+ OrderedSet([3, 1, 4, 5, 2, 0, 8, 9])
+ >>> oset | {10}
+ OrderedSet([3, 1, 4, 5, 2, 0, 10])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ containers = map(list, it.chain([self], sets))
+ items = it.chain.from_iterable(containers)
+ return cls(items)
+
+ def __and__(self, other):
+ # the parent implementation of this is backwards
+ return self.intersection(other)
+
+ def intersection(self, *sets):
+ """
+ Returns elements in common between all sets. Order is defined only
+ by the first set.
+
+ Example:
+ >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3])
+ >>> print(oset)
+ OrderedSet([1, 2, 3])
+ >>> oset.intersection([2, 4, 5], [1, 2, 3, 4])
+ OrderedSet([2])
+ >>> oset.intersection()
+ OrderedSet([1, 2, 3])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ if sets:
+ common = set.intersection(*map(set, sets))
+ items = (item for item in self if item in common)
+ else:
+ items = self
+ return cls(items)
+
+ def difference(self, *sets):
+ """
+ Returns all elements that are in this set but not the others.
+
+ Example:
+ >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]))
+ OrderedSet([1, 3])
+ >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3]))
+ OrderedSet([1])
+ >>> OrderedSet([1, 2, 3]) - OrderedSet([2])
+ OrderedSet([1, 3])
+ >>> OrderedSet([1, 2, 3]).difference()
+ OrderedSet([1, 2, 3])
+ """
+ cls = self.__class__
+ if sets:
+ other = set.union(*map(set, sets))
+ items = (item for item in self if item not in other)
+ else:
+ items = self
+ return cls(items)
+
+ def issubset(self, other):
+ """
+ Report whether another set contains this set.
+
+ Example:
+ >>> OrderedSet([1, 2, 3]).issubset({1, 2})
+ False
+ >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4})
+ True
+ >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5})
+ False
+ """
+ if len(self) > len(other): # Fast check for obvious cases
+ return False
+ return all(item in other for item in self)
+
+ def issuperset(self, other):
+ """
+ Report whether this set contains another set.
+
+ Example:
+ >>> OrderedSet([1, 2]).issuperset([1, 2, 3])
+ False
+ >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3})
+ True
+ >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3})
+ False
+ """
+ if len(self) < len(other): # Fast check for obvious cases
+ return False
+ return all(item in self for item in other)
+
+ def symmetric_difference(self, other):
+ """
+ Return the symmetric difference of two OrderedSets as a new set.
+ That is, the new set will contain all elements that are in exactly
+ one of the sets.
+
+ Their order will be preserved, with elements from `self` preceding
+ elements from `other`.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.symmetric_difference(other)
+ OrderedSet([4, 5, 9, 2])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ diff1 = cls(self).difference(other)
+ diff2 = cls(other).difference(self)
+ return diff1.union(diff2)
+
+ def _update_items(self, items):
+ """
+ Replace the 'items' list of this OrderedSet with a new one, updating
+ self.map accordingly.
+ """
+ self.items = items
+ self.map = {item: idx for (idx, item) in enumerate(items)}
+
+ def difference_update(self, *sets):
+ """
+ Update this OrderedSet to remove items from one or more other sets.
+
+ Example:
+ >>> this = OrderedSet([1, 2, 3])
+ >>> this.difference_update(OrderedSet([2, 4]))
+ >>> print(this)
+ OrderedSet([1, 3])
+
+ >>> this = OrderedSet([1, 2, 3, 4, 5])
+ >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6]))
+ >>> print(this)
+ OrderedSet([3, 5])
+ """
+ items_to_remove = set()
+ for other in sets:
+ items_to_remove |= set(other)
+ self._update_items([item for item in self.items if item not in items_to_remove])
+
+ def intersection_update(self, other):
+ """
+ Update this OrderedSet to keep only items in another set, preserving
+ their order in this set.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.intersection_update(other)
+ >>> print(this)
+ OrderedSet([1, 3, 7])
+ """
+ other = set(other)
+ self._update_items([item for item in self.items if item in other])
+
+ def symmetric_difference_update(self, other):
+ """
+ Update this OrderedSet to remove items from another set, then
+ add items from the other set that were not present in this set.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.symmetric_difference_update(other)
+ >>> print(this)
+ OrderedSet([4, 5, 9, 2])
+ """
+ items_to_add = [item for item in other if item not in self]
+ items_to_remove = set(other)
+ self._update_items(
+ [item for item in self.items if item not in items_to_remove] + items_to_add
+ )
diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt
index 7d77863d..5731b424 100644
--- a/setuptools/_vendor/vendored.txt
+++ b/setuptools/_vendor/vendored.txt
@@ -1,3 +1,4 @@
packaging==16.8
pyparsing==2.2.1
six==1.10.0
+ordered-set==3.1.1
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
index 60a8a32f..daa8e4fe 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -1,7 +1,6 @@
import os
import sys
import itertools
-import imp
from distutils.command.build_ext import build_ext as _du_build_ext
from distutils.file_util import copy_file
from distutils.ccompiler import new_compiler
@@ -12,6 +11,13 @@ from distutils import log
from setuptools.extension import Library
from setuptools.extern import six
+if six.PY2:
+ import imp
+
+ EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION]
+else:
+ from importlib.machinery import EXTENSION_SUFFIXES
+
try:
# Attempt to use Cython for building extensions, if available
from Cython.Distutils.build_ext import build_ext as _build_ext
@@ -64,7 +70,7 @@ if_dl = lambda s: s if have_rtld else ''
def get_abi3_suffix():
"""Return the file extension for an abi3-compliant Extension()"""
- for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION):
+ for suffix in EXTENSION_SUFFIXES:
if '.abi3' in suffix: # Unix
return suffix
elif suffix == '.pyd': # Windows
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 06c98271..d7b7566c 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -1180,8 +1180,7 @@ class easy_install(Command):
# to the setup.cfg file.
ei_opts = self.distribution.get_option_dict('easy_install').copy()
fetch_directives = (
- 'find_links', 'site_dirs', 'index_url', 'optimize',
- 'site_dirs', 'allow_hosts',
+ 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
)
fetch_options = {}
for key, val in ei_opts.items():
diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py
index 2b31c3e3..07d65933 100644
--- a/setuptools/command/install_lib.py
+++ b/setuptools/command/install_lib.py
@@ -1,5 +1,5 @@
import os
-import imp
+import sys
from itertools import product, starmap
import distutils.command.install_lib as orig
@@ -74,10 +74,10 @@ class install_lib(orig.install_lib):
yield '__init__.pyc'
yield '__init__.pyo'
- if not hasattr(imp, 'get_tag'):
+ if not hasattr(sys, 'implementation'):
return
- base = os.path.join('__pycache__', '__init__.' + imp.get_tag())
+ base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag)
yield base + '.pyc'
yield base + '.pyo'
yield base + '.opt-1.pyc'
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index 973e4eb2..c148b38d 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -74,7 +74,7 @@ class NonDataProperty:
class test(Command):
"""Command to run unit tests after in-place build"""
- description = "run unit tests after in-place build"
+ description = "run unit tests after in-place build (deprecated)"
user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"),
@@ -214,6 +214,14 @@ class test(Command):
return itertools.chain(ir_d, tr_d, er_d)
def run(self):
+ self.announce(
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.",
+ log.WARN,
+ )
+
installed_dists = self.install_dists(self.distribution)
cmd = ' '.join(self._argv)
diff --git a/setuptools/config.py b/setuptools/config.py
index b6626043..2d50e25e 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -12,6 +12,7 @@ from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
from setuptools.extern.packaging.version import LegacyVersion, parse
+from setuptools.extern.packaging.specifiers import SpecifierSet
from setuptools.extern.six import string_types, PY3
@@ -554,6 +555,7 @@ class ConfigOptionsHandler(ConfigHandler):
'packages': self._parse_packages,
'entry_points': self._parse_file,
'py_modules': parse_list,
+ 'python_requires': SpecifierSet,
}
def _parse_packages(self, value):
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 9a165de0..2e5ad4bd 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -27,6 +27,7 @@ from distutils.version import StrictVersion
from setuptools.extern import six
from setuptools.extern import packaging
+from setuptools.extern import ordered_set
from setuptools.extern.six.moves import map, filter, filterfalse
from . import SetuptoolsDeprecationWarning
@@ -54,7 +55,8 @@ def get_metadata_version(self):
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):
+ getattr(self, 'python_requires', None) is not None or
+ self.project_urls):
mv = StrictVersion('1.2')
elif (self.provides or self.requires or self.obsoletes or
self.classifiers or self.download_url):
@@ -212,8 +214,12 @@ def check_importable(dist, attr, value):
def assert_string_list(dist, attr, value):
- """Verify that value is a string list or None"""
+ """Verify that value is a string list"""
try:
+ # verify that value is a list or tuple to exclude unordered
+ # or single-use iterables
+ assert isinstance(value, (list, tuple))
+ # verify that elements of value are strings
assert ''.join(value) != value
except (TypeError, ValueError, AttributeError, AssertionError):
raise DistutilsSetupError(
@@ -306,20 +312,17 @@ def check_test_suite(dist, attr, value):
def check_package_data(dist, attr, value):
"""Verify that value is a dictionary of package names to glob lists"""
- if isinstance(value, dict):
- for k, v in value.items():
- if not isinstance(k, str):
- break
- try:
- iter(v)
- except TypeError:
- break
- else:
- return
- raise DistutilsSetupError(
- attr + " must be a dictionary mapping package names to lists of "
- "wildcard patterns"
- )
+ if not isinstance(value, dict):
+ raise DistutilsSetupError(
+ "{!r} must be a dictionary mapping package names to lists of "
+ "string wildcard patterns".format(attr))
+ for k, v in value.items():
+ if not isinstance(k, six.string_types):
+ raise DistutilsSetupError(
+ "keys of {!r} dict must be strings (got {!r})"
+ .format(attr, k)
+ )
+ assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
def check_packages(dist, attr, value):
@@ -405,7 +408,7 @@ class Distribution(_Distribution):
_DISTUTILS_UNSUPPORTED_METADATA = {
'long_description_content_type': None,
'project_urls': dict,
- 'provides_extras': set,
+ 'provides_extras': ordered_set.OrderedSet,
}
_patched_dist = None
diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py
index cb2fa329..e8c616f9 100644
--- a/setuptools/extern/__init__.py
+++ b/setuptools/extern/__init__.py
@@ -69,5 +69,5 @@ class VendorImporter:
sys.meta_path.append(self)
-names = 'six', 'packaging', 'pyparsing',
+names = 'six', 'packaging', 'pyparsing', 'ordered_set',
VendorImporter(__name__, names, 'setuptools._vendor').install()
diff --git a/setuptools/py33compat.py b/setuptools/py33compat.py
index 87cf5398..cb694436 100644
--- a/setuptools/py33compat.py
+++ b/setuptools/py33compat.py
@@ -52,4 +52,8 @@ class Bytecode_compat:
Bytecode = getattr(dis, 'Bytecode', Bytecode_compat)
-unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape)
+unescape = getattr(html, 'unescape', None)
+if unescape is None:
+ # HTMLParser.unescape is deprecated since Python 3.4, and will be removed
+ # from 3.9.
+ unescape = html_parser.HTMLParser().unescape
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index bc97664d..69d8d00d 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,4 +1,4 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import contextlib
@@ -819,6 +819,40 @@ class TestOptions:
]
assert sorted(dist.data_files) == sorted(expected)
+ def test_python_requires_simple(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=>=2.7
+ """),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_compound(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=>=2.7,!=3.0.*
+ """),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_invalid(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=invalid
+ """),
+ )
+ with pytest.raises(Exception):
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
saved_dist_init = _Distribution.__init__
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index 390c3dfc..36237f24 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -3,7 +3,14 @@
from __future__ import unicode_literals
import io
-from setuptools.dist import DistDeprecationWarning, _get_unpatched
+import collections
+import re
+from distutils.errors import DistutilsSetupError
+from setuptools.dist import (
+ _get_unpatched,
+ check_package_data,
+ DistDeprecationWarning,
+)
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
@@ -263,3 +270,61 @@ def test_maintainer_author(name, attrs, tmpdir):
else:
line = '%s: %s' % (fkey, val)
assert line in pkg_lines_set
+
+
+def test_provides_extras_deterministic_order():
+ extras = collections.OrderedDict()
+ extras['a'] = ['foo']
+ extras['b'] = ['bar']
+ attrs = dict(extras_require=extras)
+ dist = Distribution(attrs)
+ assert dist.metadata.provides_extras == ['a', 'b']
+ attrs['extras_require'] = collections.OrderedDict(
+ reversed(list(attrs['extras_require'].items())))
+ dist = Distribution(attrs)
+ assert dist.metadata.provides_extras == ['b', 'a']
+
+
+CHECK_PACKAGE_DATA_TESTS = (
+ # Valid.
+ ({
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ }, None),
+ # Not a dictionary.
+ ((
+ ('', ['*.txt', '*.rst']),
+ ('hello', ['*.msg']),
+ ), (
+ "'package_data' must be a dictionary mapping package"
+ " names to lists of string wildcard patterns"
+ )),
+ # Invalid key type.
+ ({
+ 400: ['*.txt', '*.rst'],
+ }, (
+ "keys of 'package_data' dict must be strings (got 400)"
+ )),
+ # Invalid value type.
+ ({
+ 'hello': str('*.msg'),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')"
+ )),
+ # Invalid value type (generators are single use)
+ ({
+ 'hello': (x for x in "generator"),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings "
+ "(got <generator object"
+ )),
+)
+
+
+@pytest.mark.parametrize('package_data, expected_message', CHECK_PACKAGE_DATA_TESTS)
+def test_check_package_data(package_data, expected_message):
+ if expected_message is None:
+ assert check_package_data(None, 'package_data', package_data) is None
+ else:
+ with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)):
+ check_package_data(None, str('package_data'), package_data)
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index db9c3873..316eb2ed 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -622,6 +622,7 @@ class TestEggInfo:
assert expected_line in pkg_info_lines
expected_line = 'Project-URL: Link Two, https://example.com/two/'
assert expected_line in pkg_info_lines
+ assert 'Metadata-Version: 1.2' in pkg_info_lines
def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires(
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 1e132188..1c0b2b18 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -143,6 +143,7 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
'tests_require',
'python_requires',
'install_requires',
+ 'long_description_content_type',
]
assert not match or match.group(1).strip('"\'') in allowed_unknowns
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index faaa6ba9..6242a018 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -1,7 +1,8 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import mock
from distutils import log
import os
@@ -85,9 +86,7 @@ def test_test(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
@@ -119,8 +118,55 @@ def test_tests_are_run_once(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_warns_deprecation(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.announce = mock.Mock()
+ cmd.run()
+ capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox."
+ )
+ cmd.announce.assert_any_call(msg, log.WARN)
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_deprecation_stderr(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ out, err = capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.\n"
+ )
+ assert msg in err
diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py
index d7b98c77..74a1284c 100644
--- a/setuptools/tests/test_virtualenv.py
+++ b/setuptools/tests/test_virtualenv.py
@@ -8,6 +8,8 @@ from pytest_fixture_config import yield_requires_config
import pytest_virtualenv
+from setuptools.extern import six
+
from .textwrap import DALS
from .test_easy_install import make_nspkg_sdist
@@ -75,9 +77,12 @@ def _get_pip_versions():
'pip==10.0.1',
'pip==18.1',
'pip==19.0.1',
- 'https://github.com/pypa/pip/archive/master.zip',
]
+ # Pip's master dropped support for 3.4.
+ if not six.PY34:
+ network_versions.append('https://github.com/pypa/pip/archive/master.zip')
+
versions = [None] + [
pytest.param(v, **({} if network else {'marks': pytest.mark.skip}))
for v in network_versions
diff --git a/tests/manual_test.py b/tests/manual_test.py
deleted file mode 100644
index 99db4b01..00000000
--- a/tests/manual_test.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import os
-import shutil
-import tempfile
-import subprocess
-from distutils.command.install import INSTALL_SCHEMES
-from string import Template
-
-from setuptools.extern.six.moves import urllib
-
-
-def _system_call(*args):
- assert subprocess.call(args) == 0
-
-
-def tempdir(func):
- def _tempdir(*args, **kwargs):
- test_dir = tempfile.mkdtemp()
- old_dir = os.getcwd()
- os.chdir(test_dir)
- try:
- return func(*args, **kwargs)
- finally:
- os.chdir(old_dir)
- shutil.rmtree(test_dir)
-
- return _tempdir
-
-
-SIMPLE_BUILDOUT = """\
-[buildout]
-
-parts = eggs
-
-[eggs]
-recipe = zc.recipe.egg
-
-eggs =
- extensions
-"""
-
-BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py'
-PYVER = sys.version.split()[0][:3]
-
-_VARS = {'base': '.',
- 'py_version_short': PYVER}
-
-scheme = 'nt' if sys.platform == 'win32' else 'unix_prefix'
-PURELIB = INSTALL_SCHEMES[scheme]['purelib']
-
-
-@tempdir
-def test_virtualenv():
- """virtualenv with setuptools"""
- purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS))
- _system_call('virtualenv', '--no-site-packages', '.')
- _system_call('bin/easy_install', 'setuptools==dev')
- # linux specific
- site_pkg = os.listdir(purelib)
- site_pkg.sort()
- assert 'setuptools' in site_pkg[0]
- easy_install = os.path.join(purelib, 'easy-install.pth')
- with open(easy_install) as f:
- res = f.read()
- assert 'setuptools' in res
-
-
-@tempdir
-def test_full():
- """virtualenv + pip + buildout"""
- _system_call('virtualenv', '--no-site-packages', '.')
- _system_call('bin/easy_install', '-q', 'setuptools==dev')
- _system_call('bin/easy_install', '-qU', 'setuptools==dev')
- _system_call('bin/easy_install', '-q', 'pip')
- _system_call('bin/pip', 'install', '-q', 'zc.buildout')
-
- with open('buildout.cfg', 'w') as f:
- f.write(SIMPLE_BUILDOUT)
-
- with open('bootstrap.py', 'w') as f:
- f.write(urllib.request.urlopen(BOOTSTRAP).read())
-
- _system_call('bin/python', 'bootstrap.py')
- _system_call('bin/buildout', '-q')
- eggs = os.listdir('eggs')
- eggs.sort()
- assert len(eggs) == 3
- assert eggs[1].startswith('setuptools')
- del eggs[1]
- assert eggs == [
- 'extensions-0.3-py2.6.egg',
- 'zc.recipe.egg-1.2.2-py2.6.egg',
- ]
-
-
-if __name__ == '__main__':
- test_virtualenv()
- test_full()
diff --git a/tests/requirements.txt b/tests/requirements.txt
index f944df27..cb3e6726 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -3,8 +3,7 @@ pytest-flake8; python_version!="3.4"
pytest-flake8<=1.0.0; python_version=="3.4"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
-# pytest pinned to <4 due to #1638
-pytest>=3.7,<4
+pytest>=3.7
wheel
coverage>=4.5.1
pytest-cov>=2.5.1