Skip to content

Commit

Permalink
Merge branch 'pkg_resources-static-direct-imports' of https://github.…
Browse files Browse the repository at this point in the history
…com/Avasam/setuptools into pkg_resources-Fix-type-of-pre-declared-variables
  • Loading branch information
Avasam committed May 9, 2024
2 parents 7df3648 + 6c5da6c commit 594900a
Show file tree
Hide file tree
Showing 33 changed files with 76 additions and 5,412 deletions.
3 changes: 2 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ exclude = (?x)(
| ^setuptools/_distutils/ # Vendored
| ^setuptools/config/_validate_pyproject/ # Auto-generated
)

# Ignoring attr-defined because setuptools wraps a lot of distutils classes, adding new attributes,
# w/o updating all the attributes and return types from the base classes for type-checkers to understand
# Especially with setuptools.dist.command vs distutils.dist.command vs setuptools._distutils.dist.command
# *.extern modules that actually live in *._vendor will also cause attr-defined issues on import
[mypy-setuptools.*]
disable_error_code = attr-defined

# - Avoid raising issues when importing from "extern" modules, as those are added to path dynamically.
Expand Down
3 changes: 3 additions & 0 deletions newsfragments/4262.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improved `AttributeError` error message if ``pkg_resources.EntryPoint.require`` is called without extras or distribution
Gracefully "do nothing" when trying to activate a ``pkg_resources.Distribution`` with a `None` location, rather than raising a `TypeError`
-- by :user:`Avasam`
3 changes: 3 additions & 0 deletions newsfragments/4322.removal.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove ``setuptools.convert_path`` after long deprecation period.
This function was never defined by ``setuptools`` itself, but rather a
side-effect of an import for internal usage.
3 changes: 3 additions & 0 deletions newsfragments/4322.removal.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove fallback for customisations of ``distutils``' ``build.sub_command`` after long
deprecated period.
Users are advised to import ``build`` directly from ``setuptools.command.build``.
1 change: 1 addition & 0 deletions newsfragments/4324.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed ``typing_extensions`` from vendored dependencies -- by :user:`Avasam`
1 change: 1 addition & 0 deletions newsfragments/4348.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix an error with `UnicodeDecodeError` handling in ``pkg_resources`` when trying to read files in UTF-8 with a fallback -- by :user:`Avasam`
1 change: 1 addition & 0 deletions newsfragments/4348.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update dynamic module imports in ``pkg_resources`` to private alias static imports. Enabled ``attr-defined`` checks in mypy for ``pkg_resources`` -- by :user:`Avasam`
116 changes: 56 additions & 60 deletions pkg_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import re
import types
from typing import TYPE_CHECKING, List, Protocol
from typing import Callable, Dict, Iterable, Optional, TypeVar
import zipfile
import zipimport
import warnings
Expand Down Expand Up @@ -72,28 +73,22 @@
drop_comment,
join_continuation,
)
import platformdirs
import packaging
import packaging.version
import packaging.specifiers
import packaging.requirements
import packaging.markers
import packaging.utils
from packaging import markers as _packaging_markers
from packaging import requirements as _packaging_requirements
from packaging import utils as _packaging_utils
from packaging import version as _packaging_version
from platformdirs import user_cache_dir as _user_cache_dir
else:
from pkg_resources.extern.jaraco.text import (
yield_lines,
drop_comment,
join_continuation,
)

from pkg_resources.extern import platformdirs
from pkg_resources.extern import packaging

__import__('pkg_resources.extern.packaging.version')
__import__('pkg_resources.extern.packaging.specifiers')
__import__('pkg_resources.extern.packaging.requirements')
__import__('pkg_resources.extern.packaging.markers')
__import__('pkg_resources.extern.packaging.utils')
from pkg_resources.extern.packaging import markers as _packaging_markers
from pkg_resources.extern.packaging import requirements as _packaging_requirements
from pkg_resources.extern.packaging import utils as _packaging_utils
from pkg_resources.extern.packaging import version as _packaging_version
from pkg_resources.extern.platformdirs import user_cache_dir as _user_cache_dir

# declare some globals that will be defined later to
# satisfy the linters.
Expand All @@ -109,9 +104,6 @@
resource_listdir = None
resource_filename = None
resource_exists = None
_distribution_finders = None
_namespace_handlers = None
_namespace_packages = None


warnings.warn(
Expand All @@ -121,6 +113,8 @@
stacklevel=2,
)

T = TypeVar("T")


_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)

Expand All @@ -132,15 +126,15 @@ class PEP440Warning(RuntimeWarning):
"""


parse_version = packaging.version.Version
parse_version = _packaging_version.Version


_state_vars = {}
_state_vars: Dict[str, str] = {}


def _declare_state(vartype, **kw):
globals().update(kw)
_state_vars.update(dict.fromkeys(kw, vartype))
def _declare_state(vartype: str, varname: str, initial_value: T) -> T:
_state_vars[varname] = vartype
return initial_value


def __getstate__():
Expand Down Expand Up @@ -746,7 +740,7 @@ def add(self, dist, entry=None, insert=True, replace=False):
return

self.by_key[dist.key] = dist
normalized_name = packaging.utils.canonicalize_name(dist.key)
normalized_name = _packaging_utils.canonicalize_name(dist.key)
self.normalized_to_canonical_keys[normalized_name] = dist.key
if dist.key not in keys:
keys.append(dist.key)
Expand Down Expand Up @@ -935,10 +929,10 @@ def find_plugins(self, plugin_env, full_env=None, installer=None, fallback=True)
# success, no need to try any more versions of this project
break

distributions = list(distributions)
distributions.sort()
sorted_distributions = list(distributions)
sorted_distributions.sort()

return distributions, error_info
return sorted_distributions, error_info

def require(self, *requirements):
"""Ensure that distributions matching `requirements` are activated
Expand Down Expand Up @@ -1360,9 +1354,7 @@ def get_default_cache():
or a platform-relevant user cache dir for an app
named "Python-Eggs".
"""
return os.environ.get('PYTHON_EGG_CACHE') or platformdirs.user_cache_dir(
appname='Python-Eggs'
)
return os.environ.get('PYTHON_EGG_CACHE') or _user_cache_dir(appname='Python-Eggs')


def safe_name(name):
Expand All @@ -1379,8 +1371,8 @@ def safe_version(version):
"""
try:
# normalize the version
return str(packaging.version.Version(version))
except packaging.version.InvalidVersion:
return str(_packaging_version.Version(version))
except _packaging_version.InvalidVersion:
version = version.replace(' ', '.')
return re.sub('[^A-Za-z0-9.]+', '-', version)

Expand Down Expand Up @@ -1457,9 +1449,9 @@ def evaluate_marker(text, extra=None):
This implementation uses the 'pyparsing' module.
"""
try:
marker = packaging.markers.Marker(text)
marker = _packaging_markers.Marker(text)
return marker.evaluate()
except packaging.markers.InvalidMarker as e:
except _packaging_markers.InvalidMarker as e:
raise SyntaxError(e) from e


Expand Down Expand Up @@ -1650,7 +1642,7 @@ def _validate_resource_path(path):
)

def _get(self, path) -> bytes:
if hasattr(self.loader, 'get_data'):
if hasattr(self.loader, 'get_data') and self.loader:
return self.loader.get_data(path)
raise NotImplementedError(
"Can't perform this operation for loaders without 'get_data()'"
Expand Down Expand Up @@ -2040,7 +2032,9 @@ def __init__(self, importer):
self._setup_prefix()


_declare_state('dict', _distribution_finders={})
_distribution_finders: Dict[
type, Callable[[object, str, bool], Iterable["Distribution"]]
] = _declare_state('dict', '_distribution_finders', {})


def register_finder(importer_type, distribution_finder):
Expand Down Expand Up @@ -2213,8 +2207,12 @@ def resolve_egg_link(path):

register_finder(importlib.machinery.FileFinder, find_on_path)

_declare_state('dict', _namespace_handlers={})
_declare_state('dict', _namespace_packages={})
_namespace_handlers: Dict[
type, Callable[[object, str, str, types.ModuleType], Optional[str]]
] = _declare_state('dict', '_namespace_handlers', {})
_namespace_packages: Dict[Optional[str], List[str]] = _declare_state(
'dict', '_namespace_packages', {}
)


def register_namespace_handler(importer_type, namespace_handler):
Expand Down Expand Up @@ -2505,8 +2503,9 @@ def resolve(self):
raise ImportError(str(exc)) from exc

def require(self, env=None, installer=None):
if self.extras and not self.dist:
raise UnknownExtra("Can't require() without a distribution", self)
if not self.dist:
error_cls = UnknownExtra if self.extras else AttributeError
raise error_cls("Can't require() without a distribution", self)

# Get the requirements for this entry point with all its extras and
# then resolve them. We have to pass `extras` along when resolving so
Expand Down Expand Up @@ -2572,11 +2571,11 @@ def parse_group(cls, group, lines, dist=None):
def parse_map(cls, data, dist=None):
"""Parse a map of entry point groups"""
if isinstance(data, dict):
data = data.items()
_data = data.items()
else:
data = split_sections(data)
_data = split_sections(data)
maps = {}
for group, lines in data:
for group, lines in _data:
if group is None:
if not lines:
continue
Expand Down Expand Up @@ -2704,20 +2703,20 @@ def parsed_version(self):
if not hasattr(self, "_parsed_version"):
try:
self._parsed_version = parse_version(self.version)
except packaging.version.InvalidVersion as ex:
except _packaging_version.InvalidVersion as ex:
info = f"(package: {self.project_name})"
if hasattr(ex, "add_note"):
ex.add_note(info) # PEP 678
raise
raise packaging.version.InvalidVersion(f"{str(ex)} {info}") from None
raise _packaging_version.InvalidVersion(f"{str(ex)} {info}") from None

return self._parsed_version

@property
def _forgiving_parsed_version(self):
try:
return self.parsed_version
except packaging.version.InvalidVersion as ex:
except _packaging_version.InvalidVersion as ex:
self._parsed_version = parse_version(_forgiving_version(self.version))

notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678
Expand Down Expand Up @@ -2838,7 +2837,7 @@ def activate(self, path=None, replace=False):
if path is None:
path = sys.path
self.insert_on(path, replace=replace)
if path is sys.path:
if path is sys.path and self.location is not None:
fixup_namespace_packages(self.location)
for pkg in self._get_metadata('namespace_packages.txt'):
if pkg in sys.modules:
Expand Down Expand Up @@ -2890,7 +2889,7 @@ def from_filename(cls, filename, metadata=None, **kw):

def as_requirement(self):
"""Return a ``Requirement`` that matches this distribution exactly"""
if isinstance(self.parsed_version, packaging.version.Version):
if isinstance(self.parsed_version, _packaging_version.Version):
spec = "%s==%s" % (self.project_name, self.parsed_version)
else:
spec = "%s===%s" % (self.project_name, self.parsed_version)
Expand All @@ -2906,15 +2905,13 @@ def load_entry_point(self, group, name):

def get_entry_map(self, group=None):
"""Return the entry point map for `group`, or the full entry map"""
try:
ep_map = self._ep_map
except AttributeError:
ep_map = self._ep_map = EntryPoint.parse_map(
if not hasattr(self, "_ep_map"):
self._ep_map = EntryPoint.parse_map(
self._get_metadata('entry_points.txt'), self
)
if group is not None:
return ep_map.get(group, {})
return ep_map
return self._ep_map.get(group, {})
return self._ep_map

def get_entry_info(self, group, name):
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
Expand Down Expand Up @@ -3138,11 +3135,11 @@ def parse_requirements(strs):
return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))


class RequirementParseError(packaging.requirements.InvalidRequirement):
class RequirementParseError(_packaging_requirements.InvalidRequirement):
"Compatibility wrapper for InvalidRequirement"


class Requirement(packaging.requirements.Requirement):
class Requirement(_packaging_requirements.Requirement):
def __init__(self, requirement_string):
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
super().__init__(requirement_string)
Expand Down Expand Up @@ -3314,8 +3311,7 @@ def _initialize_master_working_set():
Invocation by other packages is unsupported and done
at their own risk.
"""
working_set = WorkingSet._build_master()
_declare_state('object', working_set=working_set)
working_set = _declare_state('object', 'working_set', WorkingSet._build_master())

require = working_set.require
iter_entry_points = working_set.iter_entry_points
Expand Down Expand Up @@ -3365,6 +3361,6 @@ def _read_utf8_with_fallback(file: str, fallback_encoding=LOCALE_ENCODING) -> st
"""
# TODO: Add a deadline?
# See comment in setuptools.unicode_utils._Utf8EncodingNeeded
warnings.warns(msg, PkgResourcesDeprecationWarning, stacklevel=2)
warnings.warn(msg, PkgResourcesDeprecationWarning, stacklevel=2)
with open(file, "r", encoding=fallback_encoding) as f:
return f.read()

This file was deleted.

Loading

0 comments on commit 594900a

Please sign in to comment.