diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7215487880a..51d22156e43 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 72.2.0 +current_version = 73.0.1 commit = True tag = True diff --git a/.coveragerc b/.coveragerc index 5b7fdefd2ae..c8d1cbbd6ed 100644 --- a/.coveragerc +++ b/.coveragerc @@ -17,6 +17,8 @@ disable_warnings = [report] show_missing = True exclude_also = - # jaraco/skeleton#97 - @overload + # Exclude common false positives per + # https://coverage.readthedocs.io/en/latest/excluding.html#advanced-exclusion + # Ref jaraco/skeleton#97 and jaraco/skeleton#135 + class .*\bProtocol\): if TYPE_CHECKING: diff --git a/NEWS.rst b/NEWS.rst index a2d5eeba366..3c36fbfa5aa 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,46 @@ +v73.0.1 +======= + +Bugfixes +-------- + +- Remove `abc.ABCMeta` metaclass from abstract classes. `pypa/setuptools#4503 `_ had an unintended consequence of causing potential ``TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases`` -- by :user:`Avasam` (#4579) + + +v73.0.0 +======= + +Features +-------- + +- Mark abstract base classes and methods with `abc.ABC` and `abc.abstractmethod` -- by :user:`Avasam` (#4503) +- Changed the order of type checks in ``setuptools.command.easy_install.CommandSpec.from_param`` to support any `collections.abc.Iterable` of `str` param -- by :user:`Avasam` (#4505) + + +Bugfixes +-------- + +- Prevent an error in ``bdist_wheel`` if ``compression`` is set to a `str` (even if valid) after finalizing options but before running the command. -- by :user:`Avasam` (#4383) +- Raises an exception when ``py_limited_api`` is used in a build with + ``Py_GIL_DISABLED``. This is currently not supported (python/cpython#111506). (#4420) +- Synced with pypa/distutils@30b7331 including fix for modified check on empty sources (pypa/distutils#284). + + +Deprecations and Removals +------------------------- + +- ``setuptools`` is replacing the usages of :pypi:`ordered_set` with simple + instances of ``dict[Hashable, None]``. This is done to remove the extra + dependency and it is possible because since Python 3.7, ``dict`` maintain + insertion order. (#4574) + + +Misc +---- + +- #4534, #4546, #4554, #4559, #4565 + + v72.2.0 ======= diff --git a/newsfragments/4383.bugfix.rst b/newsfragments/4383.bugfix.rst deleted file mode 100644 index e5fd603abbb..00000000000 --- a/newsfragments/4383.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Prevent an error in ``bdist_wheel`` if ``compression`` is set to a `str` (even if valid) after finalizing options but before running the command. -- by :user:`Avasam` diff --git a/newsfragments/4420.bugfix.rst b/newsfragments/4420.bugfix.rst deleted file mode 100644 index c5f75fcddbd..00000000000 --- a/newsfragments/4420.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Raises an exception when ``py_limited_api`` is used in a build with -``Py_GIL_DISABLED``. This is currently not supported (python/cpython#111506). diff --git a/newsfragments/4503.feature.rst b/newsfragments/4503.feature.rst deleted file mode 100644 index 9c2e4332429..00000000000 --- a/newsfragments/4503.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Mark abstract base classes and methods with `abc.ABC` and `abc.abstractmethod` -- by :user:`Avasam` diff --git a/newsfragments/4505.feature.rst b/newsfragments/4505.feature.rst deleted file mode 100644 index e032dd997ef..00000000000 --- a/newsfragments/4505.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Changed the order of type checks in ``setuptools.command.easy_install.CommandSpec.from_param`` to support any `collections.abc.Iterable` of `str` param -- by :user:`Avasam` diff --git a/newsfragments/4534.misc.rst b/newsfragments/4534.misc.rst deleted file mode 100644 index f7c1a1d3143..00000000000 --- a/newsfragments/4534.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Changed the import of ``ctypes.wintypes`` from ``__import__`` to a regular ``import`` statement -- by :user:`Avasam` diff --git a/newsfragments/4546.misc.rst b/newsfragments/4546.misc.rst deleted file mode 100644 index f056a2b379e..00000000000 --- a/newsfragments/4546.misc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added lower bound to test dependency on ``wheel`` (0.44.0) to avoid -small inconsistencies in ``Requires-Dist`` normalisation for ``METADATA``. diff --git a/newsfragments/4554.misc.rst b/newsfragments/4554.misc.rst deleted file mode 100644 index 9992f93441f..00000000000 --- a/newsfragments/4554.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Removed ``setputools.sandbox``'s Python 2 ``builtins.file`` support -- by :user:`Avasam` diff --git a/newsfragments/4559.misc.rst b/newsfragments/4559.misc.rst deleted file mode 100644 index 34b5a30664b..00000000000 --- a/newsfragments/4559.misc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Prevent deprecation warning from ``pypa/wheel#631`` to accidentally -trigger when validating ``pyproject.toml``. diff --git a/newsfragments/4565.misc.rst b/newsfragments/4565.misc.rst deleted file mode 100644 index 031f8d66caf..00000000000 --- a/newsfragments/4565.misc.rst +++ /dev/null @@ -1,3 +0,0 @@ -Replace ``pip install -I`` with ``pip install --force-reinstall`` in -integration tests. Additionally, remove ``wheel`` from virtual environment as -it is no longer a build dependency. diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 8bbf2493710..76aa5e77bac 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -23,7 +23,6 @@ from __future__ import annotations import sys -from abc import ABC if sys.version_info < (3, 8): # noqa: UP036 # Check for unsupported versions raise RuntimeError("Python 3.8 or later is required") @@ -306,7 +305,7 @@ def get_supported_platform(): ] -class ResolutionError(Exception, ABC): +class ResolutionError(Exception): """Abstract base for dependency resolution errors""" def __repr__(self): diff --git a/pyproject.toml b/pyproject.toml index 1ce17e63ab2..bae68252e4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ backend-path = ["."] [project] name = "setuptools" -version = "72.2.0" +version = "73.0.1" authors = [ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, ] @@ -36,11 +36,6 @@ Changelog = "https://setuptools.pypa.io/en/stable/history.html" test = [ # upstream "pytest >= 6, != 8.1.*", - "pytest-checkdocs >= 2.4", - "pytest-cov", - "pytest-mypy", - "pytest-enabler >= 2.2", - "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", # local "virtualenv>=13.0.0", @@ -81,6 +76,7 @@ test = [ # workaround for businho/pytest-ruff#28 'pytest-ruff < 0.4; platform_system == "Windows"', ] + doc = [ # upstream "sphinx >= 3.5", @@ -110,7 +106,6 @@ ssl = [] certs = [] core = [ "packaging>=24", - "ordered-set>=3.1.1", "more_itertools>=8.8", "jaraco.text>=3.7", "importlib_resources>=5.10.2; python_version < '3.9'", @@ -122,6 +117,27 @@ core = [ "platformdirs >= 2.6.2", ] +check = [ + "pytest-checkdocs >= 2.4", + "pytest-ruff >= 0.2.1; sys_platform != 'cygwin'", +] + +cover = [ + "pytest-cov", +] + +enabler = [ + "pytest-enabler >= 2.2", +] + +type = [ + # upstream + "pytest-mypy", + + # local +] + + [project.entry-points."distutils.commands"] alias = "setuptools.command.alias:alias" bdist_egg = "setuptools.command.bdist_egg:bdist_egg" diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 9be78f532bd..1c39fd9dabb 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -11,7 +11,7 @@ import os import re import sys -from abc import ABC, abstractmethod +from abc import abstractmethod from typing import TYPE_CHECKING, TypeVar, overload sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip @@ -127,7 +127,7 @@ def setup(**attrs): _Command = monkey.get_unpatched(distutils.core.Command) -class Command(_Command, ABC): +class Command(_Command): """ Setuptools internal actions are organized using a *command design pattern*. This means that each action (or group of closely related actions) executed during diff --git a/setuptools/_distutils/_modified.py b/setuptools/_distutils/_modified.py index 6532aa10731..b7bdaa2943e 100644 --- a/setuptools/_distutils/_modified.py +++ b/setuptools/_distutils/_modified.py @@ -63,7 +63,7 @@ def missing_as_newer(source): return missing == 'newer' and not os.path.exists(source) ignored = os.path.exists if missing == 'ignore' else None - return any( + return not os.path.exists(target) or any( missing_as_newer(source) or _newer(source, target) for source in filter(ignored, sources) ) diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index 0a57d60be91..115302b3e74 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -658,7 +658,7 @@ def _show_help( ) print() - for command in self.commands: + for command in commands: if isinstance(command, type) and issubclass(command, Command): klass = command else: diff --git a/setuptools/_distutils/tests/test_modified.py b/setuptools/_distutils/tests/test_modified.py index 2bd82346cf0..e35cec2d6f1 100644 --- a/setuptools/_distutils/tests/test_modified.py +++ b/setuptools/_distutils/tests/test_modified.py @@ -117,3 +117,10 @@ def test_newer_pairwise_group(groups_target): newer = newer_pairwise_group([groups_target.newer], [groups_target.target]) assert older == ([], []) assert newer == ([groups_target.newer], [groups_target.target]) + + +def test_newer_group_no_sources_no_target(tmp_path): + """ + Consider no sources and no target "newer". + """ + assert newer_group([], str(tmp_path / 'does-not-exist')) diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER b/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e38a3..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA b/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA deleted file mode 100644 index 7aea136818b..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/METADATA +++ /dev/null @@ -1,158 +0,0 @@ -Metadata-Version: 2.1 -Name: ordered-set -Version: 4.1.0 -Summary: An OrderedSet is a custom MutableSet that remembers its order, so that every -Author-email: Elia Robyn Lake -Requires-Python: >=3.7 -Description-Content-Type: text/markdown -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Dist: pytest ; extra == "dev" -Requires-Dist: black ; extra == "dev" -Requires-Dist: mypy ; extra == "dev" -Project-URL: Home, https://github.com/rspeer/ordered-set -Provides-Extra: dev - -[![Pypi](https://img.shields.io/pypi/v/ordered-set.svg)](https://pypi.python.org/pypi/ordered-set) - -An OrderedSet is a mutable data structure that is a hybrid of a list and a set. -It remembers the order of its entries, and every entry has an index number that -can be looked up. - -## Installation - -`ordered_set` is available on PyPI and packaged as a wheel. You can list it -as a dependency of your project, in whatever form that takes. - -To install it into your current Python environment: - - pip install ordered-set - -To install the code for development, after checking out the repository: - - pip install flit - flit install - -## Usage examples - -An OrderedSet is created and used like a set: - - >>> from ordered_set import OrderedSet - - >>> letters = OrderedSet('abracadabra') - - >>> letters - OrderedSet(['a', 'b', 'r', 'c', 'd']) - - >>> 'r' in letters - True - -It is efficient to find the index of an entry in an OrderedSet, or find an -entry by its index. To help with this use case, the `.add()` method returns -the index of the added item, whether it was already in the set or not. - - >>> letters.index('r') - 2 - - >>> letters[2] - 'r' - - >>> letters.add('r') - 2 - - >>> letters.add('x') - 5 - -OrderedSets implement the union (`|`), intersection (`&`), and difference (`-`) -operators like sets do. - - >>> letters |= OrderedSet('shazam') - - >>> letters - OrderedSet(['a', 'b', 'r', 'c', 'd', 'x', 's', 'h', 'z', 'm']) - - >>> letters & set('aeiou') - OrderedSet(['a']) - - >>> letters -= 'abcd' - - >>> letters - OrderedSet(['r', 'x', 's', 'h', 'z', 'm']) - -The `__getitem__()` and `index()` methods have been extended to accept any -iterable except a string, returning a list, to perform NumPy-like "fancy -indexing". - - >>> letters = OrderedSet('abracadabra') - - >>> letters[[0, 2, 3]] - ['a', 'r', 'c'] - - >>> letters.index(['a', 'r', 'c']) - [0, 2, 3] - -OrderedSet implements `__getstate__` and `__setstate__` so it can be pickled, -and implements the abstract base classes `collections.MutableSet` and -`collections.Sequence`. - -OrderedSet can be used as a generic collection type, similar to the collections -in the `typing` module like List, Dict, and Set. For example, you can annotate -a variable as having the type `OrderedSet[str]` or `OrderedSet[Tuple[int, -str]]`. - - -## OrderedSet in data science applications - -An OrderedSet can be used as a bi-directional mapping between a sparse -vocabulary and dense index numbers. As of version 3.1, it accepts NumPy arrays -of index numbers as well as lists. - -This combination of features makes OrderedSet a simple implementation of many -of the things that `pandas.Index` is used for, and many of its operations are -faster than the equivalent pandas operations. - -For further compatibility with pandas.Index, `get_loc` (the pandas method for -looking up a single index) and `get_indexer` (the pandas method for fancy -indexing in reverse) are both aliases for `index` (which handles both cases -in OrderedSet). - - -## Authors - -OrderedSet was implemented by Elia Robyn Lake (maiden name: Robyn Speer). -Jon Crall contributed changes and tests to make it fit the Python set API. -Roman Inflianskas added the original type annotations. - - -## Comparisons - -The original implementation of OrderedSet was a [recipe posted to ActiveState -Recipes][recipe] by Raymond Hettiger, released under the MIT license. - -[recipe]: https://code.activestate.com/recipes/576694-orderedset/ - -Hettiger's implementation kept its content in a doubly-linked list referenced by a -dict. As a result, looking up an item by its index was an O(N) operation, while -deletion was O(1). - -This version makes different trade-offs for the sake of efficient lookups. Its -content is a standard Python list instead of a doubly-linked list. This -provides O(1) lookups by index at the expense of O(N) deletion, as well as -slightly faster iteration. - -In Python 3.6 and later, the built-in `dict` type is inherently ordered. If you -ignore the dictionary values, that also gives you a simple ordered set, with -fast O(1) insertion, deletion, iteration and membership testing. However, `dict` -does not provide the list-like random access features of OrderedSet. You -would have to convert it to a list in O(N) to look up the index of an entry or -look up an entry by its index. - diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD b/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD deleted file mode 100644 index a9875cde4e9..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/RECORD +++ /dev/null @@ -1,8 +0,0 @@ -ordered_set-4.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -ordered_set-4.1.0.dist-info/METADATA,sha256=FqVN_VUTJTCDQ-vtnmXrbgapDjciET-54gSNJ47sro8,5340 -ordered_set-4.1.0.dist-info/RECORD,, -ordered_set-4.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -ordered_set-4.1.0.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81 -ordered_set/__init__.py,sha256=ytazgKsyBKi9uFtBt938yXxQtdat1VCC681s9s0CMqg,17146 -ordered_set/__pycache__/__init__.cpython-312.pyc,, -ordered_set/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/REQUESTED b/setuptools/_vendor/ordered_set-4.1.0.dist-info/REQUESTED deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL b/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL deleted file mode 100644 index c727d148239..00000000000 --- a/setuptools/_vendor/ordered_set-4.1.0.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: flit 3.6.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/setuptools/_vendor/ordered_set/__init__.py b/setuptools/_vendor/ordered_set/__init__.py deleted file mode 100644 index e86c70ed80e..00000000000 --- a/setuptools/_vendor/ordered_set/__init__.py +++ /dev/null @@ -1,536 +0,0 @@ -""" -An OrderedSet is a custom MutableSet that remembers its order, so that every -entry has an index that can be looked up. It can also act like a Sequence. - -Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, -and released under the MIT license. -""" -import itertools as it -from typing import ( - Any, - Dict, - Iterable, - Iterator, - List, - MutableSet, - AbstractSet, - Sequence, - Set, - TypeVar, - Union, - overload, -) - -SLICE_ALL = slice(None) -__version__ = "4.1.0" - - -T = TypeVar("T") - -# SetLike[T] is either a set of elements of type T, or a sequence, which -# we will convert to an OrderedSet by adding its elements in order. -SetLike = Union[AbstractSet[T], Sequence[T]] -OrderedSetInitializer = Union[AbstractSet[T], Sequence[T], Iterable[T]] - - -def _is_atomic(obj: Any) -> bool: - """ - Returns True for objects which are iterable but should not be iterated in - the context of indexing an OrderedSet. - - When we index by an iterable, usually that means we're being asked to look - up a list of things. - - However, in the case of the .index() method, we shouldn't handle strings - and tuples like other iterables. They're not sequences of things to look - up, they're the single, atomic thing we're trying to find. - - As an example, oset.index('hello') should give the index of 'hello' in an - OrderedSet of strings. It shouldn't give the indexes of each individual - character. - """ - return isinstance(obj, str) or isinstance(obj, tuple) - - -class OrderedSet(MutableSet[T], Sequence[T]): - """ - 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, initial: OrderedSetInitializer[T] = None): - self.items: List[T] = [] - self.map: Dict[T, int] = {} - if initial is not None: - # In terms of duck-typing, the default __ior__ is compatible with - # the types we use, but it doesn't expect all the types we - # support as values for `initial`. - self |= initial # type: ignore - - 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) - - @overload - def __getitem__(self, index: slice) -> "OrderedSet[T]": - ... - - @overload - def __getitem__(self, index: Sequence[int]) -> List[T]: - ... - - @overload - def __getitem__(self, index: int) -> T: - ... - - # concrete implementation - 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 isinstance(index, Iterable): - return [self.items[i] for i in index] - elif isinstance(index, slice) or hasattr(index, "__index__"): - 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) -> "OrderedSet[T]": - """ - 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) - - # Define the gritty details of how an OrderedSet is serialized as a pickle. - # We leave off type annotations, because the only code that should interact - # with these is a generalized tool such as pickle. - def __getstate__(self): - if len(self) == 0: - # In pickle, 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: Any) -> bool: - """ - 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 - - # Technically type-incompatible with MutableSet, because we return an - # int instead of nothing. This is also one of the things that makes - # OrderedSet convenient to use. - def add(self, key: T) -> int: - """ - 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: SetLike[T]) -> int: - """ - 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 = 0 - 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 - - @overload - def index(self, key: Sequence[T]) -> List[int]: - ... - - @overload - def index(self, key: T) -> int: - ... - - # concrete implementation - 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 isinstance(key, Iterable) and not _is_atomic(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, index=-1) -> T: - """ - Remove and return item at index (default last). - - Raises KeyError if the set is empty. - Raises IndexError if index is out of range. - - Example: - >>> oset = OrderedSet([1, 2, 3]) - >>> oset.pop() - 3 - """ - if not self.items: - raise KeyError("Set is empty") - - elem = self.items[index] - del self.items[index] - del self.map[elem] - return elem - - def discard(self, key: T) -> None: - """ - 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) -> None: - """ - Remove all items from this OrderedSet. - """ - del self.items[:] - self.map.clear() - - def __iter__(self) -> Iterator[T]: - """ - Example: - >>> list(iter(OrderedSet([1, 2, 3]))) - [1, 2, 3] - """ - return iter(self.items) - - def __reversed__(self) -> Iterator[T]: - """ - Example: - >>> list(reversed(OrderedSet([1, 2, 3]))) - [3, 2, 1] - """ - return reversed(self.items) - - def __repr__(self) -> str: - if not self: - return "%s()" % (self.__class__.__name__,) - return "%s(%r)" % (self.__class__.__name__, list(self)) - - def __eq__(self, other: Any) -> bool: - """ - 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 - """ - if isinstance(other, Sequence): - # 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: SetLike[T]) -> "OrderedSet[T]": - """ - 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: type = OrderedSet - if isinstance(self, OrderedSet): - cls = self.__class__ - containers = map(list, it.chain([self], sets)) - items = it.chain.from_iterable(containers) - return cls(items) - - def __and__(self, other: SetLike[T]) -> "OrderedSet[T]": - # the parent implementation of this is backwards - return self.intersection(other) - - def intersection(self, *sets: SetLike[T]) -> "OrderedSet[T]": - """ - 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: type = OrderedSet - items: OrderedSetInitializer[T] = self - if isinstance(self, OrderedSet): - cls = self.__class__ - if sets: - common = set.intersection(*map(set, sets)) - items = (item for item in self if item in common) - return cls(items) - - def difference(self, *sets: SetLike[T]) -> "OrderedSet[T]": - """ - 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__ - items: OrderedSetInitializer[T] = self - if sets: - other = set.union(*map(set, sets)) - items = (item for item in self if item not in other) - return cls(items) - - def issubset(self, other: SetLike[T]) -> bool: - """ - 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: SetLike[T]) -> bool: - """ - 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: SetLike[T]) -> "OrderedSet[T]": - """ - 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: type = OrderedSet - if isinstance(self, OrderedSet): - cls = self.__class__ - diff1 = cls(self).difference(other) - diff2 = cls(other).difference(self) - return diff1.union(diff2) - - def _update_items(self, items: list) -> None: - """ - 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: SetLike[T]) -> None: - """ - 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() # type: Set[T] - for other in sets: - items_as_set = set(other) # type: Set[T] - items_to_remove |= items_as_set - self._update_items([item for item in self.items if item not in items_to_remove]) - - def intersection_update(self, other: SetLike[T]) -> None: - """ - 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: SetLike[T]) -> None: - """ - 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/ordered_set/py.typed b/setuptools/_vendor/ordered_set/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/setuptools/command/bdist_rpm.py b/setuptools/command/bdist_rpm.py index abf2b88bfc0..e0d4caf2e97 100644 --- a/setuptools/command/bdist_rpm.py +++ b/setuptools/command/bdist_rpm.py @@ -1,3 +1,4 @@ +from ..dist import Distribution from ..warnings import SetuptoolsDeprecationWarning import distutils.command.bdist_rpm as orig @@ -12,6 +13,8 @@ class bdist_rpm(orig.bdist_rpm): disable eggs in RPM distributions. """ + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): SetuptoolsDeprecationWarning.emit( "Deprecated command", diff --git a/setuptools/command/build.py b/setuptools/command/build.py index fd53fae8ca5..0c5e544804f 100644 --- a/setuptools/command/build.py +++ b/setuptools/command/build.py @@ -2,12 +2,16 @@ from typing import Protocol +from ..dist import Distribution + from distutils.command.build import build as _build _ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"} class build(_build): + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + # copy to avoid sharing the object with parent class sub_commands = _build.sub_commands[:] diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index 5366b0c5c61..9db57ac8a20 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -1,3 +1,5 @@ +from ..dist import Distribution + import distutils.command.build_clib as orig from distutils import log from distutils.errors import DistutilsSetupError @@ -25,6 +27,8 @@ class build_clib(orig.build_clib): the compiler. """ + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def build_libraries(self, libraries): for lib_name, build_info in libraries: sources = build_info.get('sources') diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 794ecd3dc3e..09255a32400 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -18,7 +18,6 @@ from setuptools.command import bdist_egg from setuptools.command.sdist import sdist, walk_revctrl from setuptools.command.setopt import edit_config -from setuptools.dist import Distribution from setuptools.glob import glob from .. import _entry_points, _normalization @@ -522,7 +521,6 @@ def _safe_path(self, path): class manifest_maker(sdist): - distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution template = "MANIFEST.in" def initialize_options(self): diff --git a/setuptools/command/register.py b/setuptools/command/register.py index 575790e5f27..93ef04aa0e2 100644 --- a/setuptools/command/register.py +++ b/setuptools/command/register.py @@ -1,5 +1,7 @@ from setuptools.errors import RemovedCommandError +from ..dist import Distribution + import distutils.command.register as orig from distutils import log @@ -7,6 +9,8 @@ class register(orig.register): """Formerly used to register packages on PyPI.""" + distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution + def run(self): msg = ( "The register command has been removed, use twine to upload " diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py index b2653bd4661..e351af22f0b 100644 --- a/setuptools/command/setopt.py +++ b/setuptools/command/setopt.py @@ -1,6 +1,5 @@ import configparser import os -from abc import ABC from .. import Command from ..unicode_utils import _cfg_read_utf8_with_fallback @@ -70,7 +69,7 @@ def edit_config(filename, settings, dry_run=False): opts.write(f) -class option_base(Command, ABC): +class option_base(Command): """Abstract base class for commands that mess with config files""" user_options = [ diff --git a/setuptools/dist.py b/setuptools/dist.py index 387a43de71d..68f877decd0 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, MutableMapping from more_itertools import partition, unique_everseen -from ordered_set import OrderedSet from packaging.markers import InvalidMarker, Marker from packaging.specifiers import InvalidSpecifier, SpecifierSet from packaging.version import Version @@ -253,7 +252,7 @@ class Distribution(_Distribution): _DISTUTILS_UNSUPPORTED_METADATA = { 'long_description_content_type': lambda: None, 'project_urls': dict, - 'provides_extras': OrderedSet, + 'provides_extras': dict, # behaves like an ordered set 'license_file': lambda: None, 'license_files': lambda: None, 'install_requires': list, @@ -351,7 +350,7 @@ def _finalize_requires(self): # Setuptools allows a weird ": syntax for extras extra = extra.split(':')[0] if extra: - self.metadata.provides_extras.add(extra) + self.metadata.provides_extras.setdefault(extra) def _normalize_requires(self): """Make sure requirement-related attributes exist and are normalized""" diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 9a101b71377..7d545f1004c 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -11,7 +11,6 @@ import sys import tempfile import textwrap -from abc import ABC import pkg_resources from pkg_resources import working_set @@ -263,7 +262,7 @@ def run_setup(setup_script, args): # Normal exit, just return -class AbstractSandbox(ABC): +class AbstractSandbox: """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" _active = False diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py index 2c50ee2db7f..4f0a7349f55 100644 --- a/setuptools/tests/config/test_setupcfg.py +++ b/setuptools/tests/config/test_setupcfg.py @@ -675,7 +675,7 @@ def test_extras_require(self, tmpdir): 'pdf': ['ReportLab>=1.2', 'RXP'], 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'], } - assert dist.metadata.provides_extras == set(['pdf', 'rest']) + assert set(dist.metadata.provides_extras) == {'pdf', 'rest'} @pytest.mark.parametrize( "config", diff --git a/setuptools/tests/test_core_metadata.py b/setuptools/tests/test_core_metadata.py index 3e2ce65ea38..34828ac750d 100644 --- a/setuptools/tests/test_core_metadata.py +++ b/setuptools/tests/test_core_metadata.py @@ -188,7 +188,7 @@ def test_read_metadata(name, attrs): ('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())), + ('provides_extras', lambda s: getattr(s, 'provides_extras', {})), ] for attr, getter in tested_attrs: diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index a5a5e7606d3..597785b519e 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -77,12 +77,12 @@ def test_provides_extras_deterministic_order(): extras['b'] = ['bar'] attrs = dict(extras_require=extras) dist = Distribution(attrs) - assert dist.metadata.provides_extras == ['a', 'b'] + assert list(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'] + assert list(dist.metadata.provides_extras) == ['b', 'a'] CHECK_PACKAGE_DATA_TESTS = ( diff --git a/setuptools/tests/test_extern.py b/setuptools/tests/test_extern.py index 105279d0846..d7eb3c62c19 100644 --- a/setuptools/tests/test_extern.py +++ b/setuptools/tests/test_extern.py @@ -1,20 +1,14 @@ import importlib import pickle -import ordered_set +import packaging from setuptools import Distribution def test_reimport_extern(): - ordered_set2 = importlib.import_module(ordered_set.__name__) - assert ordered_set is ordered_set2 - - -def test_orderedset_pickle_roundtrip(): - o1 = ordered_set.OrderedSet([1, 2, 5]) - o2 = pickle.loads(pickle.dumps(o1)) - assert o1 == o2 + packaging2 = importlib.import_module(packaging.__name__) + assert packaging is packaging2 def test_distribution_picklable(): diff --git a/tox.ini b/tox.ini index 6fc71eb16ab..cbade4ff462 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,10 @@ commands = usedevelop = True extras = test + check + cover + enabler + type core pass_env = SETUPTOOLS_USE_DISTUTILS