From 1db072ef257cc0971c90c8a90eb23cae1b48e129 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 24 May 2024 15:13:10 -0400 Subject: [PATCH] Enforce modern annotations syntax --- .pre-commit-config.yaml | 2 +- pkg_resources/__init__.py | 201 +++++++++--------- pkg_resources/extern/__init__.py | 9 +- pkg_resources/tests/test_pkg_resources.py | 5 +- pyproject.toml | 2 +- ruff.toml | 23 +- setuptools/_core_metadata.py | 11 +- setuptools/build_meta.py | 6 +- setuptools/command/_requirestxt.py | 8 +- setuptools/command/build.py | 10 +- setuptools/command/build_ext.py | 14 +- setuptools/command/build_py.py | 14 +- setuptools/command/easy_install.py | 7 +- setuptools/command/editable_wheel.py | 56 +++-- setuptools/command/rotate.py | 5 +- setuptools/config/_apply_pyprojecttoml.py | 52 +++-- setuptools/config/expand.py | 58 +++-- setuptools/config/pyprojecttoml.py | 48 +++-- setuptools/config/setupcfg.py | 33 ++- setuptools/discovery.py | 34 ++- setuptools/dist.py | 20 +- setuptools/monkey.py | 6 +- setuptools/msvc.py | 6 +- setuptools/sandbox.py | 5 +- .../tests/config/test_apply_pyprojecttoml.py | 5 +- setuptools/tests/test_egg_info.py | 5 +- setuptools/tests/test_manifest.py | 5 +- setuptools/warnings.py | 20 +- tools/generate_validation_code.py | 5 +- 29 files changed, 346 insertions(+), 329 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a4a7e9166..ebd94f75ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.4.5 hooks: - id: ruff - id: ruff-format diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index c10a88e5fd..371e0e13c3 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -20,9 +20,11 @@ :mod:`importlib.metadata` and :pypi:`packaging` instead. """ +from __future__ import annotations + import sys -if sys.version_info < (3, 8): +if sys.version_info < (3, 8): # noqa: UP036 # Check for unsupported versions raise RuntimeError("Python 3.8 or later is required") import os @@ -37,18 +39,15 @@ NamedTuple, NoReturn, Sequence, - Set, Tuple, - Type, Union, TYPE_CHECKING, - List, Protocol, Callable, - Dict, Iterable, - Optional, TypeVar, + Optional, + Dict, ) import zipfile import zipimport @@ -144,7 +143,7 @@ class PEP440Warning(RuntimeWarning): parse_version = _packaging_version.Version -_state_vars: Dict[str, str] = {} +_state_vars: dict[str, str] = {} def _declare_state(vartype: str, varname: str, initial_value: _T) -> _T: @@ -325,7 +324,7 @@ def req(self): def report(self): return self._template.format(**locals()) - def with_context(self, required_by: Set[Union["Distribution", str]]): + def with_context(self, required_by: set[Distribution | str]): """ If required_by is non-empty, return a version of self that is a ContextualVersionConflict. @@ -382,7 +381,7 @@ class UnknownExtra(ResolutionError): """Distribution doesn't have an "extra feature" of the given name""" -_provider_factories: Dict[Type[_ModuleLike], _ProviderFactoryType] = {} +_provider_factories: dict[type[_ModuleLike], _ProviderFactoryType] = {} PY_MAJOR = '{}.{}'.format(*sys.version_info) EGG_DIST = 3 @@ -393,7 +392,7 @@ class UnknownExtra(ResolutionError): def register_loader_type( - loader_type: Type[_ModuleLike], provider_factory: _ProviderFactoryType + loader_type: type[_ModuleLike], provider_factory: _ProviderFactoryType ): """Register `provider_factory` to make providers for `loader_type` @@ -404,7 +403,7 @@ def register_loader_type( _provider_factories[loader_type] = provider_factory -def get_provider(moduleOrReq: Union[str, "Requirement"]): +def get_provider(moduleOrReq: str | Requirement): """Return an IResourceProvider for the named module or requirement""" if isinstance(moduleOrReq, Requirement): return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] @@ -466,7 +465,7 @@ def get_build_platform(): get_platform = get_build_platform -def compatible_platforms(provided: Optional[str], required: Optional[str]): +def compatible_platforms(provided: str | None, required: str | None): """Can code for the `provided` platform run on the `required` platform? Returns true if either platform is ``None``, or the platforms are equal. @@ -531,7 +530,7 @@ def load_entry_point(dist: _EPDistType, group: str, name: str): return get_distribution(dist).load_entry_point(group, name) -def get_entry_map(dist: _EPDistType, group: Optional[str] = None): +def get_entry_map(dist: _EPDistType, group: str | None = None): """Return the entry point map for `group`, or the full entry map""" return get_distribution(dist).get_entry_map(group) @@ -560,25 +559,25 @@ def metadata_isdir(self, name: str) -> bool: def metadata_listdir(self, name: str): """List of metadata names in the directory (like ``os.listdir()``)""" - def run_script(self, script_name: str, namespace: Dict[str, Any]): + def run_script(self, script_name: str, namespace: dict[str, Any]): """Execute the named script in the supplied namespace dictionary""" class IResourceProvider(IMetadataProvider, Protocol): """An object that provides access to package resources""" - def get_resource_filename(self, manager: "ResourceManager", resource_name: str): + def get_resource_filename(self, manager: ResourceManager, resource_name: str): """Return a true filesystem path for `resource_name` `manager` must be a ``ResourceManager``""" - def get_resource_stream(self, manager: "ResourceManager", resource_name: str): + def get_resource_stream(self, manager: ResourceManager, resource_name: str): """Return a readable file-like object for `resource_name` `manager` must be a ``ResourceManager``""" def get_resource_string( - self, manager: "ResourceManager", resource_name: str + self, manager: ResourceManager, resource_name: str ) -> bytes: """Return the contents of `resource_name` as :obj:`bytes` @@ -597,9 +596,9 @@ def resource_listdir(self, resource_name: str): class WorkingSet: """A collection of active distributions on sys.path (or a similar list)""" - def __init__(self, entries: Optional[Iterable[str]] = None): + def __init__(self, entries: Iterable[str] | None = None): """Create working set from list of path entries (default=sys.path)""" - self.entries: List[str] = [] + self.entries: list[str] = [] self.entry_keys = {} self.by_key = {} self.normalized_to_canonical_keys = {} @@ -668,11 +667,11 @@ def add_entry(self, entry: str): for dist in find_distributions(entry, True): self.add(dist, entry, False) - def __contains__(self, dist: "Distribution"): + def __contains__(self, dist: Distribution): """True if `dist` is the active distribution for its project""" return self.by_key.get(dist.key) == dist - def find(self, req: "Requirement"): + def find(self, req: Requirement): """Find a distribution matching requirement `req` If there is an active distribution for the requested project, this @@ -696,7 +695,7 @@ def find(self, req: "Requirement"): raise VersionConflict(dist, req) return dist - def iter_entry_points(self, group: str, name: Optional[str] = None): + def iter_entry_points(self, group: str, name: str | None = None): """Yield entry point objects from `group` matching `name` If `name` is None, yields all entry points in `group` from all @@ -737,8 +736,8 @@ def __iter__(self): def add( self, - dist: "Distribution", - entry: Optional[str] = None, + dist: Distribution, + entry: str | None = None, insert: bool = True, replace: bool = False, ): @@ -775,11 +774,11 @@ def add( def resolve( self, - requirements: Iterable["Requirement"], - env: Optional["Environment"] = None, - installer: Optional[_InstallerType] = None, + requirements: Iterable[Requirement], + env: Environment | None = None, + installer: _InstallerType | None = None, replace_conflicting: bool = False, - extras: Optional[Tuple[str, ...]] = None, + extras: tuple[str, ...] | None = None, ): """List all distributions needed to (recursively) meet `requirements` @@ -849,7 +848,7 @@ def resolve( def _resolve_dist( self, req, best, replace_conflicting, env, installer, required_by, to_activate - ) -> "Distribution": + ) -> Distribution: dist = best.get(req.key) if dist is None: # Find the best distribution and add it to the map @@ -880,9 +879,9 @@ def _resolve_dist( def find_plugins( self, - plugin_env: "Environment", - full_env: Optional["Environment"] = None, - installer: Optional[_InstallerType] = None, + plugin_env: Environment, + full_env: Environment | None = None, + installer: _InstallerType | None = None, fallback: bool = True, ): """Find all activatable distributions in `plugin_env` @@ -982,7 +981,7 @@ def require(self, *requirements: _NestedStr): return needed def subscribe( - self, callback: Callable[["Distribution"], object], existing: bool = True + self, callback: Callable[[Distribution], object], existing: bool = True ): """Invoke `callback` for all distributions @@ -1024,9 +1023,7 @@ class _ReqExtras(Dict["Requirement", Tuple[str, ...]]): Map each requirement to the extras that demanded it. """ - def markers_pass( - self, req: "Requirement", extras: Optional[Tuple[str, ...]] = None - ): + def markers_pass(self, req: Requirement, extras: tuple[str, ...] | None = None): """ Evaluate markers for req against each extra that demanded it. @@ -1046,9 +1043,9 @@ class Environment: def __init__( self, - search_path: Optional[Sequence[str]] = None, - platform: Optional[str] = get_supported_platform(), - python: Optional[str] = PY_MAJOR, + search_path: Sequence[str] | None = None, + platform: str | None = get_supported_platform(), + python: str | None = PY_MAJOR, ): """Snapshot distributions available on a search path @@ -1071,7 +1068,7 @@ def __init__( self.python = python self.scan(search_path) - def can_add(self, dist: "Distribution"): + def can_add(self, dist: Distribution): """Is distribution `dist` acceptable for this environment? The distribution must match the platform and python version @@ -1085,11 +1082,11 @@ def can_add(self, dist: "Distribution"): ) return py_compat and compatible_platforms(dist.platform, self.platform) - def remove(self, dist: "Distribution"): + def remove(self, dist: Distribution): """Remove `dist` from the environment""" self._distmap[dist.key].remove(dist) - def scan(self, search_path: Optional[Sequence[str]] = None): + def scan(self, search_path: Sequence[str] | None = None): """Scan `search_path` for distributions usable in this environment Any distributions found are added to the environment. @@ -1115,7 +1112,7 @@ def __getitem__(self, project_name: str): distribution_key = project_name.lower() return self._distmap.get(distribution_key, []) - def add(self, dist: "Distribution"): + def add(self, dist: Distribution): """Add `dist` if we ``can_add()`` it and it has not already been added""" if self.can_add(dist) and dist.has_version(): dists = self._distmap.setdefault(dist.key, []) @@ -1125,9 +1122,9 @@ def add(self, dist: "Distribution"): def best_match( self, - req: "Requirement", + req: Requirement, working_set: WorkingSet, - installer: Optional[Callable[["Requirement"], Any]] = None, + installer: Callable[[Requirement], Any] | None = None, replace_conflicting: bool = False, ): """Find distribution best matching `req` and usable on `working_set` @@ -1158,8 +1155,8 @@ def best_match( def obtain( self, - requirement: "Requirement", - installer: Optional[Callable[["Requirement"], Any]] = None, + requirement: Requirement, + installer: Callable[[Requirement], Any] | None = None, ): """Obtain a distribution matching `requirement` (e.g. via download) @@ -1177,7 +1174,7 @@ def __iter__(self): if self[key]: yield key - def __iadd__(self, other: Union["Distribution", "Environment"]): + def __iadd__(self, other: Distribution | Environment): """In-place addition of a distribution or environment""" if isinstance(other, Distribution): self.add(other) @@ -1189,7 +1186,7 @@ def __iadd__(self, other: Union["Distribution", "Environment"]): raise TypeError("Can't add %r to environment" % (other,)) return self - def __add__(self, other: Union["Distribution", "Environment"]): + def __add__(self, other: Distribution | Environment): """Add an environment or distribution to an environment""" new = self.__class__([], platform=None, python=None) for env in self, other: @@ -1216,15 +1213,15 @@ class ExtractionError(RuntimeError): The exception instance that caused extraction to fail """ - manager: "ResourceManager" + manager: ResourceManager cache_path: str - original_error: Optional[BaseException] + original_error: BaseException | None class ResourceManager: """Manage resource extraction and packages""" - extraction_path: Optional[str] = None + extraction_path: str | None = None def __init__(self): self.cached_files = {} @@ -1389,7 +1386,7 @@ def set_extraction_path(self, path: str): self.extraction_path = path - def cleanup_resources(self, force: bool = False) -> List[str]: + def cleanup_resources(self, force: bool = False) -> list[str]: """ Delete all extracted resource files and directories, returning a list of the file and directory names that could not be successfully removed. @@ -1496,7 +1493,7 @@ def invalid_marker(text: str): return False -def evaluate_marker(text: str, extra: Optional[str] = None): +def evaluate_marker(text: str, extra: str | None = None): """ Evaluate a PEP 508 environment marker. Return a boolean indicating the marker result in this environment. @@ -1514,10 +1511,10 @@ def evaluate_marker(text: str, extra: Optional[str] = None): class NullProvider: """Try to implement resources and metadata for arbitrary PEP 302 loaders""" - egg_name: Optional[str] = None - egg_info: Optional[str] = None - loader: Optional[_LoaderProtocol] = None - module_path: Optional[str] # Some subclasses can have a None module_path + egg_name: str | None = None + egg_info: str | None = None + loader: _LoaderProtocol | None = None + module_path: str | None # Some subclasses can have a None module_path def __init__(self, module: _ModuleLike): self.loader = getattr(module, '__loader__', None) @@ -1577,7 +1574,7 @@ def metadata_listdir(self, name: str): return self._listdir(self._fn(self.egg_info, name)) return [] - def run_script(self, script_name: str, namespace: Dict[str, Any]): + def run_script(self, script_name: str, namespace: dict[str, Any]): script = 'scripts/' + script_name if not self.has_metadata(script): raise ResolutionError( @@ -1831,7 +1828,7 @@ class MemoizedZipManifests(ZipManifests): """ class manifest_mod(NamedTuple): - manifest: Dict[str, zipfile.ZipInfo] + manifest: dict[str, zipfile.ZipInfo] mtime: float def load(self, path: str): # type: ignore[override] # ZipManifests.load is a classmethod @@ -1851,7 +1848,7 @@ def load(self, path: str): # type: ignore[override] # ZipManifests.load is a cl class ZipProvider(EggProvider): """Resource support for zips and eggs""" - eagers: Optional[List[str]] = None + eagers: list[str] | None = None _zip_manifests = MemoizedZipManifests() # ZipProvider's loader should always be a zipimporter or equivalent loader: zipimport.zipimporter @@ -2033,7 +2030,7 @@ class FileMetadata(EmptyProvider): the provided location. """ - def __init__(self, path: "StrPath"): + def __init__(self, path: StrPath): self.path = path def _get_metadata_path(self, name): @@ -2102,12 +2099,12 @@ def __init__(self, importer: zipimport.zipimporter): self._setup_prefix() -_distribution_finders: Dict[type, _DistFinderType[Any]] = _declare_state( +_distribution_finders: dict[type, _DistFinderType[Any]] = _declare_state( 'dict', '_distribution_finders', {} ) -def register_finder(importer_type: Type[_T], distribution_finder: _DistFinderType[_T]): +def register_finder(importer_type: type[_T], distribution_finder: _DistFinderType[_T]): """Register `distribution_finder` to find distributions in sys.path items `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item @@ -2156,7 +2153,7 @@ def find_eggs_in_zip( def find_nothing( - importer: Optional[object], path_item: Optional[str], only: Optional[bool] = False + importer: object | None, path_item: str | None, only: bool | None = False ): return () @@ -2164,7 +2161,7 @@ def find_nothing( register_finder(object, find_nothing) -def find_on_path(importer: Optional[object], path_item, only=False): +def find_on_path(importer: object | None, path_item, only=False): """Yield distributions accessible on a sys.path directory""" path_item = _normalize_cached(path_item) @@ -2281,16 +2278,16 @@ def resolve_egg_link(path): register_finder(importlib.machinery.FileFinder, find_on_path) -_namespace_handlers: Dict[type, _NSHandlerType[Any]] = _declare_state( +_namespace_handlers: dict[type, _NSHandlerType[Any]] = _declare_state( 'dict', '_namespace_handlers', {} ) -_namespace_packages: Dict[Optional[str], List[str]] = _declare_state( +_namespace_packages: dict[str | None, list[str]] = _declare_state( 'dict', '_namespace_packages', {} ) def register_namespace_handler( - importer_type: Type[_T], namespace_handler: _NSHandlerType[_T] + importer_type: type[_T], namespace_handler: _NSHandlerType[_T] ): """Register `namespace_handler` to declare namespace packages @@ -2423,7 +2420,7 @@ def declare_namespace(packageName: str): _imp.release_lock() -def fixup_namespace_packages(path_item: str, parent: Optional[str] = None): +def fixup_namespace_packages(path_item: str, parent: str | None = None): """Ensure that previously-declared namespace packages include path_item""" _imp.acquire_lock() try: @@ -2437,7 +2434,7 @@ def fixup_namespace_packages(path_item: str, parent: Optional[str] = None): def file_ns_handler( importer: object, - path_item: "StrPath", + path_item: StrPath, packageName: str, module: types.ModuleType, ): @@ -2462,9 +2459,9 @@ def file_ns_handler( def null_ns_handler( importer: object, - path_item: Optional[str], - packageName: Optional[str], - module: Optional[_ModuleLike], + path_item: str | None, + packageName: str | None, + module: _ModuleLike | None, ): return None @@ -2472,12 +2469,12 @@ def null_ns_handler( register_namespace_handler(object, null_ns_handler) -def normalize_path(filename: "StrPath"): +def normalize_path(filename: StrPath): """Normalize a file/dir name for comparison purposes""" return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) -def _cygwin_patch(filename: "StrPath"): # pragma: nocover +def _cygwin_patch(filename: StrPath): # pragma: nocover """ Contrary to POSIX 2008, on Cygwin, getcwd (3) contains symlink components. Using @@ -2549,7 +2546,7 @@ def __init__( module_name: str, attrs: Iterable[str] = (), extras: Iterable[str] = (), - dist: Optional["Distribution"] = None, + dist: Distribution | None = None, ): if not MODULE(module_name): raise ValueError("Invalid module name", module_name) @@ -2573,8 +2570,8 @@ def __repr__(self): def load( self, require: bool = True, - *args: Optional[Union[Environment, _InstallerType]], - **kwargs: Optional[Union[Environment, _InstallerType]], + *args: Environment | _InstallerType | None, + **kwargs: Environment | _InstallerType | None, ): """ Require packages for this EntryPoint, then resolve it. @@ -2604,8 +2601,8 @@ def resolve(self): def require( self, - env: Optional[Environment] = None, - installer: Optional[_InstallerType] = None, + env: Environment | None = None, + installer: _InstallerType | None = None, ): if not self.dist: error_cls = UnknownExtra if self.extras else AttributeError @@ -2630,7 +2627,7 @@ def require( ) @classmethod - def parse(cls, src: str, dist: Optional["Distribution"] = None): + def parse(cls, src: str, dist: Distribution | None = None): """Parse a single entry point from string `src` Entry point syntax follows the form:: @@ -2663,7 +2660,7 @@ def parse_group( cls, group: str, lines: _NestedStr, - dist: Optional["Distribution"] = None, + dist: Distribution | None = None, ): """Parse an entry point group""" if not MODULE(group): @@ -2679,15 +2676,15 @@ def parse_group( @classmethod def parse_map( cls, - data: Union[str, Iterable[str], Dict[str, Union[str, Iterable[str]]]], - dist: Optional["Distribution"] = None, + data: str | Iterable[str] | dict[str, str | Iterable[str]], + dist: Distribution | None = None, ): """Parse a map of entry point groups""" if isinstance(data, dict): _data = data.items() else: _data = split_sections(data) - maps: Dict[str, Dict[str, EntryPoint]] = {} + maps: dict[str, dict[str, EntryPoint]] = {} for group, lines in _data: if group is None: if not lines: @@ -2722,12 +2719,12 @@ class Distribution: def __init__( self, - location: Optional[str] = None, + location: str | None = None, metadata: _MetadataType = None, - project_name: Optional[str] = None, - version: Optional[str] = None, - py_version: Optional[str] = PY_MAJOR, - platform: Optional[str] = None, + project_name: str | None = None, + version: str | None = None, + py_version: str | None = PY_MAJOR, + platform: str | None = None, precedence: int = EGG_DIST, ): self.project_name = safe_name(project_name or 'Unknown') @@ -2784,16 +2781,16 @@ def hashcmp(self): def __hash__(self): return hash(self.hashcmp) - def __lt__(self, other: "Distribution"): + def __lt__(self, other: Distribution): return self.hashcmp < other.hashcmp - def __le__(self, other: "Distribution"): + def __le__(self, other: Distribution): return self.hashcmp <= other.hashcmp - def __gt__(self, other: "Distribution"): + def __gt__(self, other: Distribution): return self.hashcmp > other.hashcmp - def __ge__(self, other: "Distribution"): + def __ge__(self, other: Distribution): return self.hashcmp >= other.hashcmp def __eq__(self, other: object): @@ -2951,7 +2948,7 @@ def _get_version(self): lines = self._get_metadata(self.PKG_INFO) return _version_from_file(lines) - def activate(self, path: Optional[List[str]] = None, replace: bool = False): + def activate(self, path: list[str] | None = None, replace: bool = False): """Ensure distribution is importable on `path` (default=sys.path)""" if path is None: path = sys.path @@ -3027,7 +3024,7 @@ def load_entry_point(self, group: str, name: str): raise ImportError("Entry point %r not found" % ((group, name),)) return ep.load() - def get_entry_map(self, group: Optional[str] = None): + def get_entry_map(self, group: str | None = None): """Return the entry point map for `group`, or the full entry map""" if not hasattr(self, "_ep_map"): self._ep_map = EntryPoint.parse_map( @@ -3044,7 +3041,7 @@ def get_entry_info(self, group: str, name: str): # FIXME: 'Distribution.insert_on' is too complex (13) def insert_on( # noqa: C901 self, - path: List[str], + path: list[str], loc=None, replace: bool = False, ): @@ -3152,7 +3149,7 @@ def has_version(self): return False return True - def clone(self, **kw: Optional[Union[str, int, IResourceProvider]]): + def clone(self, **kw: str | int | IResourceProvider | None): """Copy this distribution, substituting in any changed keyword args""" names = 'project_name version py_version platform location precedence' for attr in names.split(): @@ -3278,7 +3275,7 @@ def __init__(self, requirement_string: str): self.project_name, self.key = project_name, project_name.lower() self.specs = [(spec.operator, spec.version) for spec in self.specifier] # packaging.requirements.Requirement uses a set for its extras. We use a variable-length tuple - self.extras: Tuple[str] = tuple(map(safe_extra, self.extras)) + self.extras: tuple[str] = tuple(map(safe_extra, self.extras)) self.hashCmp = ( self.key, self.url, @@ -3294,7 +3291,7 @@ def __eq__(self, other: object): def __ne__(self, other): return not self == other - def __contains__(self, item: Union[Distribution, str, Tuple[str, ...]]): + def __contains__(self, item: Distribution | str | tuple[str, ...]): if isinstance(item, Distribution): if item.key != self.key: return False @@ -3313,7 +3310,7 @@ def __repr__(self): return "Requirement.parse(%r)" % str(self) @staticmethod - def parse(s: Union[str, Iterable[str]]): + def parse(s: str | Iterable[str]): (req,) = parse_requirements(s) return req diff --git a/pkg_resources/extern/__init__.py b/pkg_resources/extern/__init__.py index a1b7490dfb..9b9ac10aa9 100644 --- a/pkg_resources/extern/__init__.py +++ b/pkg_resources/extern/__init__.py @@ -1,8 +1,9 @@ +from __future__ import annotations from importlib.machinery import ModuleSpec import importlib.util import sys from types import ModuleType -from typing import Iterable, Optional, Sequence +from typing import Iterable, Sequence class VendorImporter: @@ -15,7 +16,7 @@ def __init__( self, root_name: str, vendored_names: Iterable[str] = (), - vendor_pkg: Optional[str] = None, + vendor_pkg: str | None = None, ): self.root_name = root_name self.vendored_names = set(vendored_names) @@ -65,8 +66,8 @@ def exec_module(self, module: ModuleType): def find_spec( self, fullname: str, - path: Optional[Sequence[str]] = None, - target: Optional[ModuleType] = None, + path: Sequence[str] | None = None, + target: ModuleType | None = None, ): """Return a module spec for vendored names.""" return ( diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 4724c82860..17e1ff0c2f 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import builtins import sys import tempfile @@ -9,7 +11,6 @@ import stat import distutils.dist import distutils.command.install_egg_info -from typing import List from unittest import mock @@ -33,7 +34,7 @@ def __call__(self): class TestZipProvider: - finalizers: List[EggRemover] = [] + finalizers: list[EggRemover] = [] ref_time = datetime.datetime(2013, 5, 12, 13, 25, 0) "A reference time for a file modification" diff --git a/pyproject.toml b/pyproject.toml index 7e9e66df9f..0ff3a18a40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ testing = [ "pytest-mypy", "pytest-enabler >= 2.2", # workaround for pypa/setuptools#3921 - 'pytest-ruff >= 0.2.1; sys_platform != "cygwin"', + 'pytest-ruff >= 0.3.2; sys_platform != "cygwin"', # local "virtualenv>=13.0.0", diff --git a/ruff.toml b/ruff.toml index 6f620cb890..731e52e0e2 100644 --- a/ruff.toml +++ b/ruff.toml @@ -4,10 +4,18 @@ extend-select = [ "W", # local - "UP", # pyupgrade - "YTT", # flake8-2020 + "FA", # flake8-future-annotations + "F404", # late-future-import + "UP", # pyupgrade + "YTT", # flake8-2020 ] ignore = [ + "UP015", # redundant-open-modes, explicit is preferred + "UP030", # temporarily disabled + "UP031", # temporarily disabled + "UP032", # temporarily disabled + "UP038", # Using `X | Y` in `isinstance` call is slower and more verbose https://github.com/astral-sh/ruff/issues/7871 + # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", "E111", @@ -24,19 +32,16 @@ ignore = [ "ISC001", "ISC002", ] -extend-ignore = [ - "UP015", # redundant-open-modes, explicit is preferred - "UP030", # temporarily disabled - "UP031", # temporarily disabled - "UP032", # temporarily disabled - "UP036", # temporarily disabled -] exclude = [ "**/_vendor", "setuptools/_distutils", "setuptools/config/_validate_pyproject", ] +[lint.per-file-ignores] +# Auto-generated code +"setuptools/config/_validate_pyproject/*" = ["FA100"] + [format] exclude = [ "**/_vendor", diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py index 9b4f38ded2..f1de9c9ba6 100644 --- a/setuptools/_core_metadata.py +++ b/setuptools/_core_metadata.py @@ -4,13 +4,14 @@ See: https://packaging.python.org/en/latest/specifications/core-metadata/ """ +from __future__ import annotations + import os import stat import textwrap from email import message_from_file from email.message import Message from tempfile import NamedTemporaryFile -from typing import Optional, List from distutils.util import rfc822_escape @@ -38,7 +39,7 @@ def rfc822_unescape(content: str) -> str: return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:])))) -def _read_field_from_msg(msg: Message, field: str) -> Optional[str]: +def _read_field_from_msg(msg: Message, field: str) -> str | None: """Read Message header field.""" value = msg[field] if value == 'UNKNOWN': @@ -46,7 +47,7 @@ def _read_field_from_msg(msg: Message, field: str) -> Optional[str]: return value -def _read_field_unescaped_from_msg(msg: Message, field: str) -> Optional[str]: +def _read_field_unescaped_from_msg(msg: Message, field: str) -> str | None: """Read Message header field and apply rfc822_unescape.""" value = _read_field_from_msg(msg, field) if value is None: @@ -54,7 +55,7 @@ def _read_field_unescaped_from_msg(msg: Message, field: str) -> Optional[str]: return rfc822_unescape(value) -def _read_list_from_msg(msg: Message, field: str) -> Optional[List[str]]: +def _read_list_from_msg(msg: Message, field: str) -> list[str] | None: """Read Message header field and return all results as list.""" values = msg.get_all(field, None) if values == []: @@ -62,7 +63,7 @@ def _read_list_from_msg(msg: Message, field: str) -> Optional[List[str]]: return values -def _read_payload_from_msg(msg: Message) -> Optional[str]: +def _read_payload_from_msg(msg: Message) -> str | None: value = str(msg.get_payload()).strip() if value == 'UNKNOWN' or not value: return None diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 5799c06ed6..17473f56ea 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -26,6 +26,8 @@ Again, this is not a formal definition! Just a "taste" of the module. """ +from __future__ import annotations + import io import os import shlex @@ -163,7 +165,7 @@ class _ConfigSettingsTranslator: # See pypa/setuptools#1928 pypa/setuptools#2491 - def _get_config(self, key: str, config_settings: _ConfigSettings) -> List[str]: + def _get_config(self, key: str, config_settings: _ConfigSettings) -> list[str]: """ Get the value of a specific key in ``config_settings`` as a list of strings. @@ -420,7 +422,7 @@ def build_sdist(self, sdist_directory, config_settings=None): ['sdist', '--formats', 'gztar'], '.tar.gz', sdist_directory, config_settings ) - def _get_dist_info_dir(self, metadata_directory: Optional[str]) -> Optional[str]: + def _get_dist_info_dir(self, metadata_directory: str | None) -> str | None: if not metadata_directory: return None dist_info_candidates = list(Path(metadata_directory).glob("*.dist-info")) diff --git a/setuptools/command/_requirestxt.py b/setuptools/command/_requirestxt.py index b0c2d7059a..1f1967e7aa 100644 --- a/setuptools/command/_requirestxt.py +++ b/setuptools/command/_requirestxt.py @@ -7,10 +7,12 @@ See https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#requires-txt """ +from __future__ import annotations + import io from collections import defaultdict from itertools import filterfalse -from typing import Dict, List, Tuple, Mapping, TypeVar +from typing import Dict, Mapping, TypeVar from .. import _reqs from ..extern.jaraco.text import yield_lines @@ -26,7 +28,7 @@ def _prepare( install_requires: _StrOrIter, extras_require: Mapping[str, _StrOrIter] -) -> Tuple[List[str], Dict[str, List[str]]]: +) -> tuple[list[str], dict[str, list[str]]]: """Given values for ``install_requires`` and ``extras_require`` create modified versions in a way that can be written in ``requires.txt`` """ @@ -54,7 +56,7 @@ def _convert_extras_requirements( def _move_install_requirements_markers( install_requires: _StrOrIter, extras_require: Mapping[str, _Ordered[Requirement]] -) -> Tuple[List[str], Dict[str, List[str]]]: +) -> tuple[list[str], dict[str, list[str]]]: """ The ``requires.txt`` file has an specific format: - Environment markers need to be part of the section headers and diff --git a/setuptools/command/build.py b/setuptools/command/build.py index 16c077b7cc..bc765a17ae 100644 --- a/setuptools/command/build.py +++ b/setuptools/command/build.py @@ -1,4 +1,6 @@ -from typing import Dict, List, Protocol +from __future__ import annotations + +from typing import Protocol from distutils.command.build import build as _build _ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext", "build_scripts"} @@ -87,7 +89,7 @@ def finalize_options(self): def run(self): """(Required by the original :class:`setuptools.Command` interface)""" - def get_source_files(self) -> List[str]: + def get_source_files(self) -> list[str]: """ Return a list of all files that are used by the command to create the expected outputs. @@ -98,7 +100,7 @@ def get_source_files(self) -> List[str]: All files should be strings relative to the project root directory. """ - def get_outputs(self) -> List[str]: + def get_outputs(self) -> list[str]: """ Return a list of files intended for distribution as they would have been produced by the build. @@ -111,7 +113,7 @@ def get_outputs(self) -> List[str]: and don't correspond to any source file already present in the project. """ - def get_output_mapping(self) -> Dict[str, str]: + def get_output_mapping(self) -> dict[str, str]: """ Return a mapping between destination files as they would be produced by the build (dict keys) into the respective existing (source) files (dict values). diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index 6056fe9b24..9d8aa7fcdc 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os import sys import itertools from importlib.machinery import EXTENSION_SUFFIXES from importlib.util import cache_from_source as _compiled_file_name -from typing import Dict, Iterator, List, Tuple +from typing import Iterator from pathlib import Path from distutils.command.build_ext import build_ext as _du_build_ext @@ -93,7 +95,7 @@ def run(self): if old_inplace: self.copy_extensions_to_source() - def _get_inplace_equivalent(self, build_py, ext: Extension) -> Tuple[str, str]: + def _get_inplace_equivalent(self, build_py, ext: Extension) -> tuple[str, str]: fullname = self.get_ext_fullname(ext.name) filename = self.get_ext_filename(fullname) modpath = fullname.split('.') @@ -125,7 +127,7 @@ def _get_equivalent_stub(self, ext: Extension, output_file: str) -> str: _, _, name = ext.name.rpartition(".") return f"{os.path.join(dir_, name)}.py" - def _get_output_mapping(self) -> Iterator[Tuple[str, str]]: + def _get_output_mapping(self) -> Iterator[tuple[str, str]]: if not self.inplace: return @@ -265,7 +267,7 @@ def links_to_dynamic(self, ext): pkg = '.'.join(ext._full_name.split('.')[:-1] + ['']) return any(pkg + libname in libnames for libname in ext.libraries) - def get_source_files(self) -> List[str]: + def get_source_files(self) -> list[str]: return [*_build_ext.get_source_files(self), *self._get_internal_depends()] def _get_internal_depends(self) -> Iterator[str]: @@ -306,12 +308,12 @@ def skip(orig_path: str, reason: str) -> None: yield path.as_posix() - def get_outputs(self) -> List[str]: + def get_outputs(self) -> list[str]: if self.inplace: return list(self.get_output_mapping().keys()) return sorted(_build_ext.get_outputs(self) + self.__get_stubs_outputs()) - def get_output_mapping(self) -> Dict[str, str]: + def get_output_mapping(self) -> dict[str, str]: """See :class:`setuptools.commands.build.SubCommand`""" mapping = self._get_output_mapping() return dict(sorted(mapping, key=lambda x: x[0])) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 3f40b060b3..e74946f601 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import partial from glob import glob from distutils.util import convert_path @@ -9,7 +11,7 @@ import itertools import stat from pathlib import Path -from typing import Dict, Iterable, Iterator, List, Optional, Tuple +from typing import Iterable, Iterator from ..extern.more_itertools import unique_everseen from ..warnings import SetuptoolsDeprecationWarning @@ -33,7 +35,7 @@ class build_py(orig.build_py): """ editable_mode: bool = False - existing_egg_info_dir: Optional[str] = None #: Private API, internal use only. + existing_egg_info_dir: str | None = None #: Private API, internal use only. def finalize_options(self): orig.build_py.finalize_options(self) @@ -130,13 +132,13 @@ def find_data_files(self, package, src_dir): ) return self.exclude_data_files(package, src_dir, files) - def get_outputs(self, include_bytecode=1) -> List[str]: + def get_outputs(self, include_bytecode=1) -> list[str]: """See :class:`setuptools.commands.build.SubCommand`""" if self.editable_mode: return list(self.get_output_mapping().keys()) return super().get_outputs(include_bytecode) - def get_output_mapping(self) -> Dict[str, str]: + def get_output_mapping(self) -> dict[str, str]: """See :class:`setuptools.commands.build.SubCommand`""" mapping = itertools.chain( self._get_package_data_output_mapping(), @@ -144,14 +146,14 @@ def get_output_mapping(self) -> Dict[str, str]: ) return dict(sorted(mapping, key=lambda x: x[0])) - def _get_module_mapping(self) -> Iterator[Tuple[str, str]]: + def _get_module_mapping(self) -> Iterator[tuple[str, str]]: """Iterate over all modules producing (dest, src) pairs.""" for package, module, module_file in self.find_all_modules(): package = package.split('.') filename = self.get_module_outfile(self.build_lib, package, module) yield (filename, module_file) - def _get_package_data_output_mapping(self) -> Iterator[Tuple[str, str]]: + def _get_package_data_output_mapping(self) -> Iterator[tuple[str, str]]: """Iterate over package data producing (dest, src) pairs.""" for package, src_dir, build_dir, filenames in self.data_files: for filename in filenames: diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 41ff382fe4..df4d34570d 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -10,6 +10,8 @@ """ +from __future__ import annotations + from glob import glob from distutils.util import get_platform from distutils.util import convert_path, subst_vars @@ -25,7 +27,6 @@ from distutils.command import install import sys import os -from typing import Dict, List import zipimport import shutil import tempfile @@ -2038,8 +2039,8 @@ class CommandSpec(list): those passed to Popen. """ - options: List[str] = [] - split_args: Dict[str, bool] = dict() + options: list[str] = [] + split_args: dict[str, bool] = dict() @classmethod def best(cls): diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index a835a8194b..55d477eebf 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -10,6 +10,8 @@ *auxiliary build directory* or ``auxiliary_dir``. """ +from __future__ import annotations + import logging import io import os @@ -23,14 +25,10 @@ from tempfile import TemporaryDirectory from typing import ( TYPE_CHECKING, - Dict, Iterable, Iterator, - List, Mapping, - Optional, Protocol, - Tuple, TypeVar, cast, ) @@ -78,7 +76,7 @@ class _EditableMode(Enum): COMPAT = "compat" # TODO: Remove `compat` after Dec/2022. @classmethod - def convert(cls, mode: Optional[str]) -> "_EditableMode": + def convert(cls, mode: str | None) -> _EditableMode: if not mode: return _EditableMode.LENIENT # default @@ -180,7 +178,7 @@ def _install_namespaces(self, installation_dir, pth_prefix): installer = _NamespaceInstaller(dist, installation_dir, pth_prefix, src_root) installer.install_namespaces() - def _find_egg_info_dir(self) -> Optional[str]: + def _find_egg_info_dir(self) -> str | None: parent_dir = Path(self.dist_info_dir).parent if self.dist_info_dir else Path() candidates = map(str, parent_dir.glob("*.egg-info")) return next(candidates, None) @@ -255,9 +253,9 @@ def _set_editable_mode(self): elif hasattr(cmd, "inplace"): cmd.inplace = True # backward compatibility with distutils - def _collect_build_outputs(self) -> Tuple[List[str], Dict[str, str]]: - files: List[str] = [] - mapping: Dict[str, str] = {} + def _collect_build_outputs(self) -> tuple[list[str], dict[str, str]]: + files: list[str] = [] + mapping: dict[str, str] = {} build = self.get_finalized_command("build") for cmd_name in build.get_sub_commands(): @@ -275,7 +273,7 @@ def _run_build_commands( unpacked_wheel: StrPath, build_lib: StrPath, tmp_dir: StrPath, - ) -> Tuple[List[str], Dict[str, str]]: + ) -> tuple[list[str], dict[str, str]]: self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir) self._run_build_subcommands() files, mapping = self._collect_build_outputs() @@ -373,7 +371,7 @@ def _select_strategy( name: str, tag: str, build_lib: StrPath, - ) -> "EditableStrategy": + ) -> EditableStrategy: """Decides which strategy to use to implement an editable installation.""" build_name = f"__editable__.{name}-{tag}" project_dir = Path(self.project_dir) @@ -396,9 +394,7 @@ def _select_strategy( class EditableStrategy(Protocol): - def __call__( - self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str] - ): ... + def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): ... def __enter__(self): ... @@ -406,12 +402,12 @@ def __exit__(self, _exc_type, _exc_value, _traceback): ... class _StaticPth: - def __init__(self, dist: Distribution, name: str, path_entries: List[Path]): + def __init__(self, dist: Distribution, name: str, path_entries: list[Path]): self.dist = dist self.name = name self.path_entries = path_entries - def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): entries = "\n".join(str(p.resolve()) for p in self.path_entries) contents = _encode_pth(f"{entries}\n") wheel.writestr(f"__editable__.{self.name}.pth", contents) @@ -451,11 +447,11 @@ def __init__( self._file = dist.get_command_obj("build_py").copy_file # type: ignore[union-attr] super().__init__(dist, name, [self.auxiliary_dir]) - def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): self._create_links(files, mapping) super().__call__(wheel, files, mapping) - def _normalize_output(self, file: str) -> Optional[str]: + def _normalize_output(self, file: str) -> str | None: # Files relative to build_lib will be normalized to None with suppress(ValueError): path = Path(file).resolve().relative_to(self.build_lib) @@ -505,13 +501,13 @@ def __init__(self, dist: Distribution, name: str): self.dist = dist self.name = name - def template_vars(self) -> Tuple[str, str, Dict[str, str], Dict[str, List[str]]]: + def template_vars(self) -> tuple[str, str, dict[str, str], dict[str, list[str]]]: src_root = self.dist.src_root or os.curdir top_level = chain(_find_packages(self.dist), _find_top_level_modules(self.dist)) package_dir = self.dist.package_dir or {} roots = _find_package_roots(top_level, package_dir, src_root) - namespaces_: Dict[str, List[str]] = dict( + namespaces_: dict[str, list[str]] = dict( chain( _find_namespaces(self.dist.packages or [], roots), ((ns, []) for ns in _find_virtual_namespaces(roots)), @@ -532,7 +528,7 @@ def template_vars(self) -> Tuple[str, str, Dict[str, str], Dict[str, List[str]]] finder = _normalization.safe_identifier(name) return finder, name, mapping, namespaces_ - def get_implementation(self) -> Iterator[Tuple[str, bytes]]: + def get_implementation(self) -> Iterator[tuple[str, bytes]]: finder, name, mapping, namespaces_ = self.template_vars() content = bytes(_finder_template(name, mapping, namespaces_), "utf-8") @@ -541,7 +537,7 @@ def get_implementation(self) -> Iterator[Tuple[str, bytes]]: content = _encode_pth(f"import {finder}; {finder}.install()") yield (f"__editable__.{self.name}.pth", content) - def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]): + def __call__(self, wheel: WheelFile, files: list[str], mapping: dict[str, str]): for file, content in self.get_implementation(): wheel.writestr(file, content) @@ -597,7 +593,7 @@ def _can_symlink_files(base_dir: Path) -> bool: def _simple_layout( - packages: Iterable[str], package_dir: Dict[str, str], project_dir: StrPath + packages: Iterable[str], package_dir: dict[str, str], project_dir: StrPath ) -> bool: """Return ``True`` if: - all packages are contained by the same parent directory, **and** @@ -680,8 +676,8 @@ def _find_package_roots( packages: Iterable[str], package_dir: Mapping[str, str], src_root: StrPath, -) -> Dict[str, str]: - pkg_roots: Dict[str, str] = { +) -> dict[str, str]: + pkg_roots: dict[str, str] = { pkg: _absolute_root(find_package_path(pkg, package_dir, src_root)) for pkg in sorted(packages) } @@ -700,7 +696,7 @@ def _absolute_root(path: StrPath) -> str: return str(parent.resolve() / path_.name) -def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]: +def _find_virtual_namespaces(pkg_roots: dict[str, str]) -> Iterator[str]: """By carefully designing ``package_dir``, it is possible to implement the logical structure of PEP 420 in a package without the corresponding directories. @@ -725,15 +721,15 @@ def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]: def _find_namespaces( - packages: List[str], pkg_roots: Dict[str, str] -) -> Iterator[Tuple[str, List[str]]]: + packages: list[str], pkg_roots: dict[str, str] +) -> Iterator[tuple[str, list[str]]]: for pkg in packages: path = find_package_path(pkg, pkg_roots, "") if Path(path).exists() and not Path(path, "__init__.py").exists(): yield (pkg, [path]) -def _remove_nested(pkg_roots: Dict[str, str]) -> Dict[str, str]: +def _remove_nested(pkg_roots: dict[str, str]) -> dict[str, str]: output = dict(pkg_roots.copy()) for pkg, path in reversed(list(pkg_roots.items())): @@ -883,7 +879,7 @@ def install(): def _finder_template( - name: str, mapping: Mapping[str, str], namespaces: Dict[str, List[str]] + name: str, mapping: Mapping[str, str], namespaces: dict[str, list[str]] ) -> str: """Create a string containing the code for the``MetaPathFinder`` and ``PathEntryFinder``. diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py index 6f73721c70..064d7959ff 100644 --- a/setuptools/command/rotate.py +++ b/setuptools/command/rotate.py @@ -1,9 +1,10 @@ +from __future__ import annotations + from distutils.util import convert_path from distutils import log from distutils.errors import DistutilsOptionError import os import shutil -from typing import List from setuptools import Command @@ -18,7 +19,7 @@ class rotate(Command): ('keep=', 'k', "number of matching distributions to keep"), ] - boolean_options: List[str] = [] + boolean_options: list[str] = [] def initialize_options(self): self.match = None diff --git a/setuptools/config/_apply_pyprojecttoml.py b/setuptools/config/_apply_pyprojecttoml.py index 3626282a79..c7e25b755f 100644 --- a/setuptools/config/_apply_pyprojecttoml.py +++ b/setuptools/config/_apply_pyprojecttoml.py @@ -8,6 +8,8 @@ **PRIVATE MODULE**: API reserved for setuptools internal usage only. """ +from __future__ import annotations + import logging import os from collections.abc import Mapping @@ -20,12 +22,6 @@ TYPE_CHECKING, Any, Callable, - Dict, - List, - Optional, - Set, - Tuple, - Type, Union, cast, ) @@ -46,7 +42,7 @@ _logger = logging.getLogger(__name__) -def apply(dist: "Distribution", config: dict, filename: StrPath) -> "Distribution": +def apply(dist: Distribution, config: dict, filename: StrPath) -> Distribution: """Apply configuration dict read with :func:`read_configuration`""" if not config: @@ -68,7 +64,7 @@ def apply(dist: "Distribution", config: dict, filename: StrPath) -> "Distributio return dist -def _apply_project_table(dist: "Distribution", config: dict, root_dir: StrPath): +def _apply_project_table(dist: Distribution, config: dict, root_dir: StrPath): project_table = config.get("project", {}).copy() if not project_table: return # short-circuit @@ -85,7 +81,7 @@ def _apply_project_table(dist: "Distribution", config: dict, root_dir: StrPath): _set_config(dist, corresp, value) -def _apply_tool_table(dist: "Distribution", config: dict, filename: StrPath): +def _apply_tool_table(dist: Distribution, config: dict, filename: StrPath): tool_table = config.get("tool", {}).get("setuptools", {}) if not tool_table: return # short-circuit @@ -107,7 +103,7 @@ def _apply_tool_table(dist: "Distribution", config: dict, filename: StrPath): _copy_command_options(config, dist, filename) -def _handle_missing_dynamic(dist: "Distribution", project_table: dict): +def _handle_missing_dynamic(dist: Distribution, project_table: dict): """Be temporarily forgiving with ``dynamic`` fields not listed in ``dynamic``""" dynamic = set(project_table.get("dynamic", [])) for field, getter in _PREVIOUSLY_DEFINED.items(): @@ -123,7 +119,7 @@ def json_compatible_key(key: str) -> str: return key.lower().replace("-", "_") -def _set_config(dist: "Distribution", field: str, value: Any): +def _set_config(dist: Distribution, field: str, value: Any): setter = getattr(dist.metadata, f"set_{field}", None) if setter: setter(value) @@ -140,7 +136,7 @@ def _set_config(dist: "Distribution", field: str, value: Any): } -def _guess_content_type(file: str) -> Optional[str]: +def _guess_content_type(file: str) -> str | None: _, ext = os.path.splitext(file.lower()) if not ext: return None @@ -153,11 +149,11 @@ def _guess_content_type(file: str) -> Optional[str]: raise ValueError(f"Undefined content type for {file}, {msg}") -def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: StrPath): +def _long_description(dist: Distribution, val: _DictOrStr, root_dir: StrPath): from setuptools.config import expand if isinstance(val, str): - file: Union[str, list] = val + file: str | list = val text = expand.read_files(file, root_dir) ctype = _guess_content_type(val) else: @@ -174,7 +170,7 @@ def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: StrPath): dist._referenced_files.add(cast(str, file)) -def _license(dist: "Distribution", val: dict, root_dir: StrPath): +def _license(dist: Distribution, val: dict, root_dir: StrPath): from setuptools.config import expand if "file" in val: @@ -184,7 +180,7 @@ def _license(dist: "Distribution", val: dict, root_dir: StrPath): _set_config(dist, "license", val["text"]) -def _people(dist: "Distribution", val: List[dict], _root_dir: StrPath, kind: str): +def _people(dist: Distribution, val: list[dict], _root_dir: StrPath, kind: str): field = [] email_field = [] for person in val: @@ -202,24 +198,24 @@ def _people(dist: "Distribution", val: List[dict], _root_dir: StrPath, kind: str _set_config(dist, f"{kind}_email", ", ".join(email_field)) -def _project_urls(dist: "Distribution", val: dict, _root_dir): +def _project_urls(dist: Distribution, val: dict, _root_dir): _set_config(dist, "project_urls", val) -def _python_requires(dist: "Distribution", val: dict, _root_dir): +def _python_requires(dist: Distribution, val: dict, _root_dir): from setuptools.extern.packaging.specifiers import SpecifierSet _set_config(dist, "python_requires", SpecifierSet(val)) -def _dependencies(dist: "Distribution", val: list, _root_dir): +def _dependencies(dist: Distribution, val: list, _root_dir): if getattr(dist, "install_requires", []): msg = "`install_requires` overwritten in `pyproject.toml` (dependencies)" SetuptoolsWarning.emit(msg) dist.install_requires = val -def _optional_dependencies(dist: "Distribution", val: dict, _root_dir): +def _optional_dependencies(dist: Distribution, val: dict, _root_dir): existing = getattr(dist, "extras_require", None) or {} dist.extras_require = {**existing, **val} @@ -244,7 +240,7 @@ def _unify_entry_points(project_table: dict): # intentional (for resetting configurations that are missing `dynamic`). -def _copy_command_options(pyproject: dict, dist: "Distribution", filename: StrPath): +def _copy_command_options(pyproject: dict, dist: Distribution, filename: StrPath): tool_table = pyproject.get("tool", {}) cmdclass = tool_table.get("setuptools", {}).get("cmdclass", {}) valid_options = _valid_command_options(cmdclass) @@ -263,7 +259,7 @@ def _copy_command_options(pyproject: dict, dist: "Distribution", filename: StrPa _logger.warning(f"Command option {cmd}.{key} is not defined") -def _valid_command_options(cmdclass: Mapping = EMPTY) -> Dict[str, Set[str]]: +def _valid_command_options(cmdclass: Mapping = EMPTY) -> dict[str, set[str]]: from .._importlib import metadata from setuptools.dist import Distribution @@ -280,7 +276,7 @@ def _valid_command_options(cmdclass: Mapping = EMPTY) -> Dict[str, Set[str]]: return valid_options -def _load_ep(ep: "metadata.EntryPoint") -> Optional[Tuple[str, Type]]: +def _load_ep(ep: metadata.EntryPoint) -> tuple[str, type] | None: # Ignore all the errors try: return (ep.name, ep.load()) @@ -294,22 +290,22 @@ def _normalise_cmd_option_key(name: str) -> str: return json_compatible_key(name).strip("_=") -def _normalise_cmd_options(desc: "_OptionsList") -> Set[str]: +def _normalise_cmd_options(desc: _OptionsList) -> set[str]: return {_normalise_cmd_option_key(fancy_option[0]) for fancy_option in desc} -def _get_previous_entrypoints(dist: "Distribution") -> Dict[str, list]: +def _get_previous_entrypoints(dist: Distribution) -> dict[str, list]: ignore = ("console_scripts", "gui_scripts") value = getattr(dist, "entry_points", None) or {} return {k: v for k, v in value.items() if k not in ignore} -def _get_previous_scripts(dist: "Distribution") -> Optional[list]: +def _get_previous_scripts(dist: Distribution) -> list | None: value = getattr(dist, "entry_points", None) or {} return value.get("console_scripts") -def _get_previous_gui_scripts(dist: "Distribution") -> Optional[list]: +def _get_previous_gui_scripts(dist: Distribution) -> list | None: value = getattr(dist, "entry_points", None) or {} return value.get("gui_scripts") @@ -349,7 +345,7 @@ def _acessor(obj): return _acessor -PYPROJECT_CORRESPONDENCE: Dict[str, _Correspondence] = { +PYPROJECT_CORRESPONDENCE: dict[str, _Correspondence] = { "readme": _long_description, "license": _license, "authors": partial(_people, kind="author"), diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index 0d8d58add8..a1f5deb285 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -18,6 +18,8 @@ **PRIVATE MODULE**: API reserved for setuptools internal usage only. """ +from __future__ import annotations + import ast import importlib import os @@ -30,13 +32,9 @@ from typing import ( TYPE_CHECKING, Callable, - Dict, Iterable, Iterator, - List, Mapping, - Optional, - Tuple, TypeVar, Union, cast, @@ -67,7 +65,7 @@ def __init__(self, name: str, spec: ModuleSpec): vars(self).update(locals()) del self.self - def _find_assignments(self) -> Iterator[Tuple[ast.AST, ast.AST]]: + def _find_assignments(self) -> Iterator[tuple[ast.AST, ast.AST]]: for statement in self.module.body: if isinstance(statement, ast.Assign): yield from ((target, statement.value) for target in statement.targets) @@ -87,8 +85,8 @@ def __getattr__(self, attr): def glob_relative( - patterns: Iterable[str], root_dir: Optional[StrPath] = None -) -> List[str]: + patterns: Iterable[str], root_dir: StrPath | None = None +) -> list[str]: """Expand the list of glob patterns, but preserving relative paths. :param list[str] patterns: List of glob patterns @@ -119,7 +117,7 @@ def glob_relative( return expanded_values -def read_files(filepaths: Union[str, bytes, Iterable[StrPath]], root_dir=None) -> str: +def read_files(filepaths: str | bytes | Iterable[StrPath], root_dir=None) -> str: """Return the content of the files concatenated using ``\n`` as str This function is sandboxed and won't reach anything outside ``root_dir`` @@ -145,7 +143,7 @@ def _filter_existing_files(filepaths: Iterable[StrPath]) -> Iterator[StrPath]: SetuptoolsWarning.emit(f"File {path!r} cannot be found") -def _read_file(filepath: Union[bytes, StrPath]) -> str: +def _read_file(filepath: bytes | StrPath) -> str: with open(filepath, encoding='utf-8') as f: return f.read() @@ -160,8 +158,8 @@ def _assert_local(filepath: StrPath, root_dir: str): def read_attr( attr_desc: str, - package_dir: Optional[Mapping[str, str]] = None, - root_dir: Optional[StrPath] = None, + package_dir: Mapping[str, str] | None = None, + root_dir: StrPath | None = None, ): """Reads the value of an attribute from a module. @@ -196,7 +194,7 @@ def read_attr( return getattr(module, attr_name) -def _find_spec(module_name: str, module_path: Optional[StrPath]) -> ModuleSpec: +def _find_spec(module_name: str, module_path: StrPath | None) -> ModuleSpec: spec = importlib.util.spec_from_file_location(module_name, module_path) spec = spec or importlib.util.find_spec(module_name) @@ -217,8 +215,8 @@ def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType: def _find_module( - module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: StrPath -) -> Tuple[StrPath, Optional[str], str]: + module_name: str, package_dir: Mapping[str, str] | None, root_dir: StrPath +) -> tuple[StrPath, str | None, str]: """Given a module (that could normally be imported by ``module_name`` after the build is complete), find the path to the parent directory where it is contained and the canonical name that could be used to import it @@ -252,8 +250,8 @@ def _find_module( def resolve_class( qualified_class_name: str, - package_dir: Optional[Mapping[str, str]] = None, - root_dir: Optional[StrPath] = None, + package_dir: Mapping[str, str] | None = None, + root_dir: StrPath | None = None, ) -> Callable: """Given a qualified class name, return the associated class object""" root_dir = root_dir or os.getcwd() @@ -267,10 +265,10 @@ def resolve_class( def cmdclass( - values: Dict[str, str], - package_dir: Optional[Mapping[str, str]] = None, - root_dir: Optional[StrPath] = None, -) -> Dict[str, Callable]: + values: dict[str, str], + package_dir: Mapping[str, str] | None = None, + root_dir: StrPath | None = None, +) -> dict[str, Callable]: """Given a dictionary mapping command names to strings for qualified class names, apply :func:`resolve_class` to the dict values. """ @@ -280,10 +278,10 @@ def cmdclass( def find_packages( *, namespaces=True, - fill_package_dir: Optional[Dict[str, str]] = None, - root_dir: Optional[StrPath] = None, + fill_package_dir: dict[str, str] | None = None, + root_dir: StrPath | None = None, **kwargs, -) -> List[str]: +) -> list[str]: """Works similarly to :func:`setuptools.find_packages`, but with all arguments given as keyword arguments. Moreover, ``where`` can be given as a list (the results will be simply concatenated). @@ -311,7 +309,7 @@ def find_packages( root_dir = root_dir or os.curdir where = kwargs.pop('where', ['.']) - packages: List[str] = [] + packages: list[str] = [] fill_package_dir = {} if fill_package_dir is None else fill_package_dir search = list(unique_everseen(always_iterable(where))) @@ -335,7 +333,7 @@ def _nest_path(parent: StrPath, path: StrPath) -> str: return os.path.normpath(path) -def version(value: Union[Callable, Iterable[Union[str, int]], str]) -> str: +def version(value: Callable | Iterable[str | int] | str) -> str: """When getting the version directly from an attribute, it should be normalised to string. """ @@ -360,8 +358,8 @@ def canonic_package_data(package_data: dict) -> dict: def canonic_data_files( - data_files: Union[list, dict], root_dir: Optional[StrPath] = None -) -> List[Tuple[str, List[str]]]: + data_files: list | dict, root_dir: StrPath | None = None +) -> list[tuple[str, list[str]]]: """For compatibility with ``setup.py``, ``data_files`` should be a list of pairs instead of a dict. @@ -376,7 +374,7 @@ def canonic_data_files( ] -def entry_points(text: str, text_source="entry-points") -> Dict[str, dict]: +def entry_points(text: str, text_source="entry-points") -> dict[str, dict]: """Given the contents of entry-points file, process it into a 2-level dictionary (``dict[str, dict[str, str]]``). The first level keys are entry-point groups, the second level keys are @@ -401,7 +399,7 @@ class EnsurePackagesDiscovered: and those might not have been processed yet. """ - def __init__(self, distribution: "Distribution"): + def __init__(self, distribution: Distribution): self._dist = distribution self._called = False @@ -445,7 +443,7 @@ class LazyMappingProxy(Mapping[_K, _V]): def __init__(self, obtain_mapping_value: Callable[[], Mapping[_K, _V]]): self._obtain = obtain_mapping_value - self._value: Optional[Mapping[_K, _V]] = None + self._value: Mapping[_K, _V] | None = None def _target(self) -> Mapping[_K, _V]: if self._value is None: diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index d379405595..c8dae5f751 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -9,11 +9,13 @@ with the help of ``tomllib`` or ``tomli``. """ +from __future__ import annotations + import logging import os from contextlib import contextmanager from functools import partial -from typing import TYPE_CHECKING, Callable, Dict, Mapping, Optional, Set +from typing import TYPE_CHECKING, Callable, Mapping from .._path import StrPath from ..errors import FileError, InvalidConfigError @@ -58,10 +60,10 @@ def validate(config: dict, filepath: StrPath) -> bool: def apply_configuration( - dist: "Distribution", + dist: Distribution, filepath: StrPath, ignore_option_errors=False, -) -> "Distribution": +) -> Distribution: """Apply the configuration from a ``pyproject.toml`` file into an existing distribution object. """ @@ -73,7 +75,7 @@ def read_configuration( filepath: StrPath, expand=True, ignore_option_errors=False, - dist: Optional["Distribution"] = None, + dist: Distribution | None = None, ): """Read given configuration file and returns options from it as a dict. @@ -141,9 +143,9 @@ def read_configuration( def expand_configuration( config: dict, - root_dir: Optional[StrPath] = None, + root_dir: StrPath | None = None, ignore_option_errors: bool = False, - dist: Optional["Distribution"] = None, + dist: Distribution | None = None, ) -> dict: """Given a configuration with unresolved fields (e.g. dynamic, cmdclass, ...) find their final values. @@ -166,9 +168,9 @@ class _ConfigExpander: def __init__( self, config: dict, - root_dir: Optional[StrPath] = None, + root_dir: StrPath | None = None, ignore_option_errors: bool = False, - dist: Optional["Distribution"] = None, + dist: Distribution | None = None, ): self.config = config self.root_dir = root_dir or os.getcwd() @@ -178,9 +180,9 @@ def __init__( self.dynamic_cfg = self.setuptools_cfg.get("dynamic", {}) self.ignore_option_errors = ignore_option_errors self._dist = dist - self._referenced_files: Set[str] = set() + self._referenced_files: set[str] = set() - def _ensure_dist(self) -> "Distribution": + def _ensure_dist(self) -> Distribution: from setuptools.dist import Distribution attrs = {"src_root": self.root_dir, "name": self.project_cfg.get("name", None)} @@ -233,7 +235,7 @@ def _expand_cmdclass(self, package_dir: Mapping[str, str]): cmdclass = partial(_expand.cmdclass, package_dir=package_dir, root_dir=root_dir) self._process_field(self.setuptools_cfg, "cmdclass", cmdclass) - def _expand_all_dynamic(self, dist: "Distribution", package_dir: Mapping[str, str]): + def _expand_all_dynamic(self, dist: Distribution, package_dir: Mapping[str, str]): special = ( # need special handling "version", "readme", @@ -263,7 +265,7 @@ def _expand_all_dynamic(self, dist: "Distribution", package_dir: Mapping[str, st updates = {k: v for k, v in obtained_dynamic.items() if v is not None} self.project_cfg.update(updates) - def _ensure_previously_set(self, dist: "Distribution", field: str): + def _ensure_previously_set(self, dist: Distribution, field: str): previous = _PREVIOUSLY_DEFINED[field](dist) if previous is None and not self.ignore_option_errors: msg = ( @@ -288,7 +290,7 @@ def _expand_directive( raise ValueError(f"invalid `{specifier}`: {directive!r}") return None - def _obtain(self, dist: "Distribution", field: str, package_dir: Mapping[str, str]): + def _obtain(self, dist: Distribution, field: str, package_dir: Mapping[str, str]): if field in self.dynamic_cfg: return self._expand_directive( f"tool.setuptools.dynamic.{field}", @@ -298,13 +300,13 @@ def _obtain(self, dist: "Distribution", field: str, package_dir: Mapping[str, st self._ensure_previously_set(dist, field) return None - def _obtain_version(self, dist: "Distribution", package_dir: Mapping[str, str]): + def _obtain_version(self, dist: Distribution, package_dir: Mapping[str, str]): # Since plugins can set version, let's silently skip if it cannot be obtained if "version" in self.dynamic and "version" in self.dynamic_cfg: return _expand.version(self._obtain(dist, "version", package_dir)) return None - def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str, str]]: + def _obtain_readme(self, dist: Distribution) -> dict[str, str] | None: if "readme" not in self.dynamic: return None @@ -319,8 +321,8 @@ def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str, str]]: return None def _obtain_entry_points( - self, dist: "Distribution", package_dir: Mapping[str, str] - ) -> Optional[Dict[str, dict]]: + self, dist: Distribution, package_dir: Mapping[str, str] + ) -> dict[str, dict] | None: fields = ("entry-points", "scripts", "gui-scripts") if not any(field in self.dynamic for field in fields): return None @@ -344,21 +346,21 @@ def _set_scripts(field: str, group: str): return expanded - def _obtain_classifiers(self, dist: "Distribution"): + def _obtain_classifiers(self, dist: Distribution): if "classifiers" in self.dynamic: value = self._obtain(dist, "classifiers", {}) if value: return value.splitlines() return None - def _obtain_dependencies(self, dist: "Distribution"): + def _obtain_dependencies(self, dist: Distribution): if "dependencies" in self.dynamic: value = self._obtain(dist, "dependencies", {}) if value: return _parse_requirements_list(value) return None - def _obtain_optional_dependencies(self, dist: "Distribution"): + def _obtain_optional_dependencies(self, dist: Distribution): if "optional-dependencies" not in self.dynamic: return None if "optional-dependencies" in self.dynamic_cfg: @@ -400,18 +402,18 @@ def _ignore_errors(ignore_option_errors: bool): class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered): def __init__( - self, distribution: "Distribution", project_cfg: dict, setuptools_cfg: dict + self, distribution: Distribution, project_cfg: dict, setuptools_cfg: dict ): super().__init__(distribution) self._project_cfg = project_cfg self._setuptools_cfg = setuptools_cfg - def __enter__(self) -> "Self": + def __enter__(self) -> Self: """When entering the context, the values of ``packages``, ``py_modules`` and ``package_dir`` that are missing in ``dist`` are copied from ``setuptools_cfg``. """ dist, cfg = self._dist, self._setuptools_cfg - package_dir: Dict[str, str] = cfg.setdefault("package-dir", {}) + package_dir: dict[str, str] = cfg.setdefault("package-dir", {}) package_dir.update(dist.package_dir or {}) dist.package_dir = package_dir # needs to be the same object diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index 59d9cf8adb..0a7a42eb09 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -9,6 +9,8 @@ with the help of ``configparser``. """ +from __future__ import annotations + import contextlib import functools import os @@ -22,9 +24,6 @@ Dict, Generic, Iterable, - List, - Optional, - Set, Tuple, TypeVar, Union, @@ -80,7 +79,7 @@ def read_configuration( return configuration_to_dict(handlers) -def apply_configuration(dist: "Distribution", filepath: StrPath) -> "Distribution": +def apply_configuration(dist: Distribution, filepath: StrPath) -> Distribution: """Apply the configuration from a ``setup.cfg`` file into an existing distribution object. """ @@ -90,11 +89,11 @@ def apply_configuration(dist: "Distribution", filepath: StrPath) -> "Distributio def _apply( - dist: "Distribution", + dist: Distribution, filepath: StrPath, other_files: Iterable[StrPath] = (), ignore_option_errors: bool = False, -) -> Tuple["ConfigHandler", ...]: +) -> tuple[ConfigHandler, ...]: """Read configuration from ``filepath`` and applies to the ``dist`` object.""" from setuptools.dist import _Distribution @@ -131,7 +130,7 @@ def _get_option(target_obj: Target, key: str): return getter() -def configuration_to_dict(handlers: Tuple["ConfigHandler", ...]) -> dict: +def configuration_to_dict(handlers: tuple[ConfigHandler, ...]) -> dict: """Returns configuration data gathered by given handlers as a dict. :param list[ConfigHandler] handlers: Handlers list, @@ -150,10 +149,10 @@ def configuration_to_dict(handlers: Tuple["ConfigHandler", ...]) -> dict: def parse_configuration( - distribution: "Distribution", + distribution: Distribution, command_options: AllCommandOptions, ignore_option_errors=False, -) -> Tuple["ConfigMetadataHandler", "ConfigOptionsHandler"]: +) -> tuple[ConfigMetadataHandler, ConfigOptionsHandler]: """Performs additional parsing of configuration options for a distribution. @@ -236,7 +235,7 @@ class ConfigHandler(Generic[Target]): """ - aliases: Dict[str, str] = {} + aliases: dict[str, str] = {} """Options aliases. For compatibility with various packages. E.g.: d2to1 and pbr. Note: `-` in keys is replaced with `_` by config parser. @@ -253,9 +252,9 @@ def __init__( self.ignore_option_errors = ignore_option_errors self.target_obj = target_obj self.sections = dict(self._section_options(options)) - self.set_options: List[str] = [] + self.set_options: list[str] = [] self.ensure_discovered = ensure_discovered - self._referenced_files: Set[str] = set() + self._referenced_files: set[str] = set() """After parsing configurations, this property will enumerate all files referenced by the "file:" directive. Private API for setuptools only. """ @@ -485,7 +484,7 @@ def parse(self) -> None: if section_name: # [section.option] variant method_postfix = '_%s' % section_name - section_parser_method: Optional[Callable] = getattr( + section_parser_method: Callable | None = getattr( self, # Dots in section names are translated into dunderscores. ('parse_section%s' % method_postfix).replace('.', '__'), @@ -534,11 +533,11 @@ class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]): def __init__( self, - target_obj: "DistributionMetadata", + target_obj: DistributionMetadata, options: AllCommandOptions, ignore_option_errors: bool, ensure_discovered: expand.EnsurePackagesDiscovered, - package_dir: Optional[dict] = None, + package_dir: dict | None = None, root_dir: StrPath = os.curdir, ): super().__init__(target_obj, options, ignore_option_errors, ensure_discovered) @@ -598,14 +597,14 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]): def __init__( self, - target_obj: "Distribution", + target_obj: Distribution, options: AllCommandOptions, ignore_option_errors: bool, ensure_discovered: expand.EnsurePackagesDiscovered, ): super().__init__(target_obj, options, ignore_option_errors, ensure_discovered) self.root_dir = target_obj.src_root - self.package_dir: Dict[str, str] = {} # To be filled by `find_packages` + self.package_dir: dict[str, str] = {} # To be filled by `find_packages` @classmethod def _parse_list_semicolon(cls, value): diff --git a/setuptools/discovery.py b/setuptools/discovery.py index 571be12bf4..880d414033 100644 --- a/setuptools/discovery.py +++ b/setuptools/discovery.py @@ -37,6 +37,8 @@ """ +from __future__ import annotations + import itertools import os from fnmatch import fnmatchcase @@ -44,13 +46,9 @@ from pathlib import Path from typing import ( TYPE_CHECKING, - Dict, Iterable, Iterator, - List, Mapping, - Optional, - Tuple, ) import _distutils_hack.override # noqa: F401 @@ -91,8 +89,8 @@ def __contains__(self, item: str) -> bool: class _Finder: """Base class that exposes functionality for module/package finders""" - ALWAYS_EXCLUDE: Tuple[str, ...] = () - DEFAULT_EXCLUDE: Tuple[str, ...] = () + ALWAYS_EXCLUDE: tuple[str, ...] = () + DEFAULT_EXCLUDE: tuple[str, ...] = () @classmethod def find( @@ -100,7 +98,7 @@ def find( where: StrPath = '.', exclude: Iterable[str] = (), include: Iterable[str] = ('*',), - ) -> List[str]: + ) -> list[str]: """Return a list of all Python items (packages or modules, depending on the finder implementation) found within directory 'where'. @@ -291,7 +289,7 @@ class FlatLayoutModuleFinder(ModuleFinder): """Reserved top-level module names""" -def _find_packages_within(root_pkg: str, pkg_dir: StrPath) -> List[str]: +def _find_packages_within(root_pkg: str, pkg_dir: StrPath) -> list[str]: nested = PEP420PackageFinder.find(pkg_dir) return [root_pkg] + [".".join((root_pkg, n)) for n in nested] @@ -301,7 +299,7 @@ class ConfigDiscovery: (from other metadata/options, the file system or conventions) """ - def __init__(self, distribution: "Distribution"): + def __init__(self, distribution: Distribution): self.dist = distribution self._called = False self._disabled = False @@ -329,7 +327,7 @@ def _root_dir(self) -> StrPath: return self.dist.src_root or os.curdir @property - def _package_dir(self) -> Dict[str, str]: + def _package_dir(self) -> dict[str, str]: if self.dist.package_dir is None: return {} return self.dist.package_dir @@ -455,7 +453,7 @@ def _analyse_flat_modules(self) -> bool: self._ensure_no_accidental_inclusion(self.dist.py_modules, "modules") return bool(self.dist.py_modules) - def _ensure_no_accidental_inclusion(self, detected: List[str], kind: str): + def _ensure_no_accidental_inclusion(self, detected: list[str], kind: str): if len(detected) > 1: from inspect import cleandoc @@ -495,7 +493,7 @@ def analyse_name(self): if name: self.dist.metadata.name = name - def _find_name_single_package_or_module(self) -> Optional[str]: + def _find_name_single_package_or_module(self) -> str | None: """Exactly one module or package""" for field in ('packages', 'py_modules'): items = getattr(self.dist, field, None) or [] @@ -505,7 +503,7 @@ def _find_name_single_package_or_module(self) -> Optional[str]: return None - def _find_name_from_packages(self) -> Optional[str]: + def _find_name_from_packages(self) -> str | None: """Try to find the root package that is not a PEP 420 namespace""" if not self.dist.packages: return None @@ -522,7 +520,7 @@ def _find_name_from_packages(self) -> Optional[str]: return None -def remove_nested_packages(packages: List[str]) -> List[str]: +def remove_nested_packages(packages: list[str]) -> list[str]: """Remove nested packages from a list of packages. >>> remove_nested_packages(["a", "a.b1", "a.b2", "a.b1.c1"]) @@ -540,7 +538,7 @@ def remove_nested_packages(packages: List[str]) -> List[str]: return top_level -def remove_stubs(packages: List[str]) -> List[str]: +def remove_stubs(packages: list[str]) -> list[str]: """Remove type stubs (:pep:`561`) from a list of packages. >>> remove_stubs(["a", "a.b", "a-stubs", "a-stubs.b.c", "b", "c-stubs"]) @@ -550,8 +548,8 @@ def remove_stubs(packages: List[str]) -> List[str]: def find_parent_package( - packages: List[str], package_dir: Mapping[str, str], root_dir: StrPath -) -> Optional[str]: + packages: list[str], package_dir: Mapping[str, str], root_dir: StrPath +) -> str | None: """Find the parent package that is not a namespace.""" packages = sorted(packages, key=len) common_ancestors = [] @@ -607,7 +605,7 @@ def find_package_path( return os.path.join(root_dir, *parent.split("/"), *parts) -def construct_package_dir(packages: List[str], package_path: StrPath) -> Dict[str, str]: +def construct_package_dir(packages: list[str], package_path: StrPath) -> dict[str, str]: parent_pkgs = remove_nested_packages(packages) prefix = Path(package_path).parts return {pkg: "/".join([*prefix, *pkg.split(".")]) for pkg in parent_pkgs} diff --git a/setuptools/dist.py b/setuptools/dist.py index 80ae589d4f..30cdfdb10b 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -1,5 +1,4 @@ -__all__ = ['Distribution'] - +from __future__ import annotations import io import itertools @@ -10,7 +9,7 @@ from contextlib import suppress from glob import iglob from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, MutableMapping, Optional, Set, Tuple +from typing import TYPE_CHECKING, MutableMapping import distutils.cmd import distutils.command @@ -38,6 +37,7 @@ from .monkey import get_unpatched from .warnings import InformationOnly, SetuptoolsDeprecationWarning +__all__ = ['Distribution'] sequence = tuple, list @@ -287,12 +287,12 @@ def patch_missing_pkg_info(self, attrs): dist._version = _normalization.safe_version(str(attrs['version'])) self._patched_dist = dist - def __init__(self, attrs: Optional[MutableMapping] = None) -> None: + def __init__(self, attrs: MutableMapping | None = None) -> None: have_package_data = hasattr(self, "package_data") if not have_package_data: - self.package_data: Dict[str, List[str]] = {} + self.package_data: dict[str, list[str]] = {} attrs = attrs or {} - self.dist_files: List[Tuple[str, str, str]] = [] + self.dist_files: list[tuple[str, str, str]] = [] # Filter-out setuptools' specific options. self.src_root = attrs.pop("src_root", None) self.patch_missing_pkg_info(attrs) @@ -309,7 +309,7 @@ def __init__(self, attrs: Optional[MutableMapping] = None) -> None: # Private API (setuptools-use only, not restricted to Distribution) # Stores files that are referenced by the configuration and need to be in the # sdist (e.g. `version = file: VERSION.txt`) - self._referenced_files: Set[str] = set() + self._referenced_files: set[str] = set() self.set_defaults = ConfigDiscovery(self) @@ -387,10 +387,10 @@ def _normalize_requires(self): def _finalize_license_files(self) -> None: """Compute names of all license files which should be included.""" - license_files: Optional[List[str]] = self.metadata.license_files - patterns: List[str] = license_files if license_files else [] + license_files: list[str] | None = self.metadata.license_files + patterns: list[str] = license_files if license_files else [] - license_file: Optional[str] = self.metadata.license_file + license_file: str | None = self.metadata.license_file if license_file and license_file not in patterns: patterns.append(license_file) diff --git a/setuptools/monkey.py b/setuptools/monkey.py index 1f8d8ffe0f..e513f95245 100644 --- a/setuptools/monkey.py +++ b/setuptools/monkey.py @@ -2,20 +2,22 @@ Monkey patching of distutils. """ +from __future__ import annotations + import functools import inspect import platform import sys import types from importlib import import_module -from typing import List, TypeVar +from typing import TypeVar import distutils.filelist _T = TypeVar("_T") -__all__: List[str] = [] +__all__: list[str] = [] """ Everything is private. Contact the project team if you think you need this functionality. diff --git a/setuptools/msvc.py b/setuptools/msvc.py index b2a0f2bebb..f86c480d18 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -11,6 +11,8 @@ This may also support compilers shipped with compatible Visual Studio versions. """ +from __future__ import annotations + import json from os import listdir, pathsep from os.path import join, isfile, isdir, dirname @@ -20,7 +22,7 @@ import itertools import subprocess import distutils.errors -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING from setuptools.extern.more_itertools import unique_everseen # https://github.com/python/mypy/issues/8166 @@ -36,7 +38,7 @@ class winreg: HKEY_LOCAL_MACHINE = None HKEY_CLASSES_ROOT = None - environ: Dict[str, str] = dict() + environ: dict[str, str] = dict() def _msvc14_find_vc2015(): diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index e5da9d86f0..147b26749e 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys import tempfile @@ -9,7 +11,6 @@ import pickle import textwrap import builtins -from typing import Union, List import pkg_resources from distutils.errors import DistutilsError @@ -425,7 +426,7 @@ class DirectorySandbox(AbstractSandbox): "tempnam", ]) - _exception_patterns: List[Union[str, re.Pattern]] = [] + _exception_patterns: list[str | re.Pattern] = [] "exempt writing to paths that match the pattern" def __init__(self, sandbox, exceptions=_EXCEPTIONS): diff --git a/setuptools/tests/config/test_apply_pyprojecttoml.py b/setuptools/tests/config/test_apply_pyprojecttoml.py index bb78f64310..6b3ee9cf1e 100644 --- a/setuptools/tests/config/test_apply_pyprojecttoml.py +++ b/setuptools/tests/config/test_apply_pyprojecttoml.py @@ -4,12 +4,13 @@ To run these tests offline, please have a look on ``./downloads/preload.py`` """ +from __future__ import annotations + import io import re import tarfile from inspect import cleandoc from pathlib import Path -from typing import Tuple from unittest.mock import Mock from zipfile import ZipFile @@ -457,7 +458,7 @@ def core_metadata(dist) -> str: # Make sure core metadata is valid Metadata.from_email(pkg_file_txt, validate=True) # can raise exceptions - skip_prefixes: Tuple[str, ...] = () + skip_prefixes: tuple[str, ...] = () skip_lines = set() # ---- DIFF NORMALISATION ---- # PEP 621 is very particular about author/maintainer metadata conversion, so skip diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index f6b2302d97..f2489896b3 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import ast import os @@ -5,7 +7,6 @@ import re import stat import time -from typing import List, Tuple from pathlib import Path from unittest import mock @@ -77,7 +78,7 @@ def run(): }) @staticmethod - def _extract_mv_version(pkg_info_lines: List[str]) -> Tuple[int, int]: + def _extract_mv_version(pkg_info_lines: list[str]) -> tuple[int, int]: version_str = pkg_info_lines[0].split(' ')[1] major, minor = map(int, version_str.split('.')[:2]) return major, minor diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py index 6911b0224c..f3eba733d9 100644 --- a/setuptools/tests/test_manifest.py +++ b/setuptools/tests/test_manifest.py @@ -1,5 +1,7 @@ """sdist tests""" +from __future__ import annotations + import contextlib import os import shutil @@ -10,7 +12,6 @@ import logging from distutils import log from distutils.errors import DistutilsTemplateError -from typing import List, Tuple from setuptools.command.egg_info import FileList, egg_info, translate_pattern from setuptools.dist import Distribution @@ -76,7 +77,7 @@ def touch(filename): ) -translate_specs: List[Tuple[str, List[str], List[str]]] = [ +translate_specs: list[tuple[str, list[str], list[str]]] = [ ('foo', ['foo'], ['bar', 'foobar']), ('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']), # Glob matching diff --git a/setuptools/warnings.py b/setuptools/warnings.py index b3e252ca57..5d9cca6c37 100644 --- a/setuptools/warnings.py +++ b/setuptools/warnings.py @@ -5,12 +5,14 @@ setuptools. """ +from __future__ import annotations + import os import warnings from datetime import date from inspect import cleandoc from textwrap import indent -from typing import Optional, Tuple +from typing import Tuple _DueDate = Tuple[int, int, int] # time tuple _INDENT = 8 * " " @@ -23,11 +25,11 @@ class SetuptoolsWarning(UserWarning): @classmethod def emit( cls, - summary: Optional[str] = None, - details: Optional[str] = None, - due_date: Optional[_DueDate] = None, - see_docs: Optional[str] = None, - see_url: Optional[str] = None, + summary: str | None = None, + details: str | None = None, + due_date: _DueDate | None = None, + see_docs: str | None = None, + see_url: str | None = None, stacklevel: int = 2, **kwargs, ): @@ -51,9 +53,9 @@ def _format( cls, summary: str, details: str, - due_date: Optional[date] = None, - see_url: Optional[str] = None, - format_args: Optional[dict] = None, + due_date: date | None = None, + see_url: str | None = None, + format_args: dict | None = None, ): """Private: reserved for ``setuptools`` internal use only""" today = date.today() diff --git a/tools/generate_validation_code.py b/tools/generate_validation_code.py index 0171325bd0..ca933e8f3b 100644 --- a/tools/generate_validation_code.py +++ b/tools/generate_validation_code.py @@ -1,12 +1,13 @@ +from __future__ import annotations + from os import PathLike import subprocess import sys from pathlib import Path -from typing import Union -def generate_pyproject_validation(dest: Union[str, PathLike]): +def generate_pyproject_validation(dest: str | PathLike[str]): """ Generates validation code for ``pyproject.toml`` based on JSON schemas and the ``validate-pyproject`` library.