Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify vendoring and declare dependencies optionally #4457

Merged
merged 29 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
62bd80f
Declare the dependencies and update vendoring routine for setuptools …
jaraco Jul 2, 2024
e9bb687
Specify environment-conditional transitive deps.
jaraco Jul 2, 2024
d4352b5
Import dependencies naturally and ensure they're available by appendi…
jaraco Jul 2, 2024
00384a5
Re-vendor setuptools packages.
jaraco Jul 2, 2024
3ed7e27
Remove importlib_metadata workaround.
jaraco Jul 3, 2024
f21bcab
Remove setuptools.extern
jaraco Jul 3, 2024
bd5cf00
Remove check-extern env in tox.
jaraco Jul 3, 2024
9234fc3
Update vendoring routine for pkg_resources to simply install the depe…
jaraco Jul 3, 2024
d03cd0e
Import dependencies naturally and ensure they're available by appendi…
jaraco Jul 3, 2024
c6913bf
Re-vendor pkg_resources packages.
jaraco Jul 3, 2024
51615db
Remove obsolete 'rewrite' functionality from vendored script.
jaraco Jul 3, 2024
b4b6bf7
Update auto-detect the minimum python version needed for vendored pac…
jaraco Jul 3, 2024
4f6d973
Refresh vendored dependencies.
jaraco Jul 3, 2024
8b4b9d0
Consolidate vendored packages in the setuptools package.
jaraco Jul 3, 2024
1523957
Remove pkg_resources.extern.
jaraco Jul 3, 2024
c4086b9
Clean up references to extern modules.
jaraco Jul 3, 2024
ed15a3b
Add news fragment.
jaraco Jul 3, 2024
cb4b670
Suppress type errors around wheel.
jaraco Jul 3, 2024
e7c320b
Fix type error in vendored tool.
jaraco Jul 3, 2024
3fd66b9
Fix incorrect type declaration in _python_requires.
jaraco Jul 3, 2024
bb17215
Fix type errors in pkg_resources, now that packaging types are recogn…
jaraco Jul 3, 2024
242ef24
Suppress coverage errors.
jaraco Jul 3, 2024
d25f6d9
Moved the dependencies to a 'core' extra to avoid dangers with cyclic…
jaraco Jul 3, 2024
fa7ee91
Ensure that package data from vendored packages gets installed.
jaraco Jul 3, 2024
a2a64ec
Explicitly declare the 'core' extra as 'for informational purposes'
jaraco Jul 8, 2024
225f7fe
Rewrite marker evaluation to address two type check failures.
Avasam Jul 9, 2024
0e1e707
Update `Requirement.__contains__` type spec to accept `UnparsedVersion`.
Avasam Jul 9, 2024
930ebe5
Rely on os.path.join and os.path.dirname when adding the vendored pat…
jaraco Jul 9, 2024
9eb89de
Resolve a version from an item, addressing missed `arg-type` check.
jaraco Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ jobs:
job:
- diffcov
- docs
- check-extern
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
3 changes: 0 additions & 3 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@ def pytest_configure(config):
'setuptools/tests/mod_with_constant.py',
'setuptools/_distutils',
'_distutils_hack',
'setuptools/extern',
'pkg_resources/extern',
'pkg_resources/tests/data',
'setuptools/_vendor',
'pkg_resources/_vendor',
'setuptools/config/_validate_pyproject',
'setuptools/modified.py',
'setuptools/tests/bdist_wheel_testdata',
Expand Down
11 changes: 5 additions & 6 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exclude = (?x)(
| ^.tox/
| ^.eggs/
| ^pkg_resources/tests/data/my-test-package-source/setup.py$ # Duplicate module name
| ^.+?/(_vendor|extern)/ # Vendored
| ^setuptools/_vendor/ # Vendored
| ^setuptools/_distutils/ # Vendored
| ^setuptools/config/_validate_pyproject/ # Auto-generated
| ^setuptools/tests/bdist_wheel_testdata/ # Duplicate module name
Expand All @@ -31,15 +31,14 @@ disable_error_code = attr-defined
[mypy-pkg_resources.tests.*]
disable_error_code = import-not-found

# - Avoid raising issues when importing from "extern" modules, as those are added to path dynamically.
# https://github.com/pypa/setuptools/pull/3979#discussion_r1367968993
# - distutils._modified has different errors on Python 3.8 [import-untyped], on Python 3.9+ [import-not-found]
# - All jaraco modules are still untyped
# - _validate_project sometimes complains about trove_classifiers (#4296)
[mypy-pkg_resources.extern.*,setuptools.extern.*,distutils._modified,jaraco.*,trove_classifiers]
# - wheel appears to be untyped
[mypy-distutils._modified,jaraco.*,trove_classifiers,wheel.*]
ignore_missing_imports = True

# Even when excluding vendored/generated modules, there might be problems: https://github.com/python/mypy/issues/11936#issuecomment-1466764006
[mypy-setuptools._vendor.packaging._manylinux,setuptools.config._validate_pyproject.*]
# Even when excluding generated modules, there might be problems: https://github.com/python/mypy/issues/11936#issuecomment-1466764006
[mypy-setuptools.config._validate_pyproject.*]
follow_imports = silent
# silent => ignore errors when following imports
1 change: 1 addition & 0 deletions newsfragments/2825.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Now setuptools declares its own dependencies in the ``core`` extra. Dependencies are still vendored for bootstrapping purposes, but setuptools will prefer installed dependencies if present. The ``core`` extra is used for informational purposes and should *not* be declared in package metadata (e.g. ``build-requires``). Downstream packagers can de-vendor by simply removing the ``setuptools/_vendor`` directory.
43 changes: 27 additions & 16 deletions pkg_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@

import _imp

sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack breaks when this package is within a zip file... pypa/virtualenv#1727 cc @jaraco

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to hear that.

It's not fair to characterize this as a hack. It's the vendoring approach, which is required by the ecosystem to satisfy PEP 517's "no circular build dependencies" constraint. Setuptools is following best-available standards for supplying sophisticated behavior. There's a lot of work going on in pypa/packaging-problems#342 to solve the issue more holistically so that build backends can have and declare proper dependencies.

When I first authored this change, I considered the zipimport case and thought that this approach might work for the zipimport case but I never verified.

Since the release of this change, the vendoring support is there as a fallback for environments that cannot include the dependencies naturally. The best approach would be for the app/environment builder to ensure that setuptools[core] is installed (such that vendoring is unnecessary).

I'd be open to expanding the vendoring support to support better the zipimport case. I'll open an issue about it.


# capture these to bypass sandboxing
from os import utime
from os import open as os_open
Expand All @@ -87,16 +89,17 @@
# no write support, probably under GAE
WRITE_SUPPORT = False

from pkg_resources.extern.jaraco.text import (
import packaging.specifiers
from jaraco.text import (
yield_lines,
drop_comment,
join_continuation,
)
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
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

if TYPE_CHECKING:
from _typeshed import BytesPath, StrPath, StrOrBytesPath
Expand Down Expand Up @@ -538,8 +541,7 @@ def get_distribution(dist: Distribution | _PkgReqType) -> Distribution:
if isinstance(dist, str):
dist = Requirement.parse(dist)
if isinstance(dist, Requirement):
# Bad type narrowing, dist has to be a Requirement here, so get_provider has to return Distribution
dist = get_provider(dist) # type: ignore[assignment]
dist = get_provider(dist)
if not isinstance(dist, Distribution):
raise TypeError("Expected str, Requirement, or Distribution", dist)
return dist
Expand Down Expand Up @@ -1117,11 +1119,10 @@ def markers_pass(self, req: Requirement, extras: tuple[str, ...] | None = None):
Return False if the req has a marker and fails
evaluation. Otherwise, return True.
"""
extra_evals = (
return not req.marker or any(
req.marker.evaluate({'extra': extra})
for extra in self.get(req, ()) + (extras or (None,))
for extra in self.get(req, ()) + (extras or ("",))
)
return not req.marker or any(extra_evals)


class Environment:
Expand Down Expand Up @@ -3430,15 +3431,18 @@ class RequirementParseError(_packaging_requirements.InvalidRequirement):


class Requirement(_packaging_requirements.Requirement):
# prefer variable length tuple to set (as found in
# packaging.requirements.Requirement)
extras: tuple[str, ...] # type: ignore[assignment]

def __init__(self, requirement_string: str):
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
super().__init__(requirement_string)
self.unsafe_name = self.name
project_name = safe_name(self.name)
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(map(safe_extra, self.extras))
self.hashCmp = (
self.key,
self.url,
Expand All @@ -3454,17 +3458,24 @@ def __eq__(self, other: object):
def __ne__(self, other):
return not self == other

def __contains__(self, item: Distribution | str | tuple[str, ...]) -> bool:
def __contains__(
self, item: Distribution | packaging.specifiers.UnparsedVersion
) -> bool:
if isinstance(item, Distribution):
if item.key != self.key:
return False

item = item.version
version = item.version
else:
version = item

# Allow prereleases always in order to match the previous behavior of
# this method. In the future this should be smarter and follow PEP 440
# more accurately.
return self.specifier.contains(item, prereleases=True)
return self.specifier.contains(
version,
prereleases=True,
)

def __hash__(self):
return self.__hash
Expand Down

This file was deleted.

Loading
Loading