Remove setuptools runtime dependency from Pyramid#3805
Open
russellballestrini wants to merge 1 commit intoPylons:mainfrom
Open
Remove setuptools runtime dependency from Pyramid#3805russellballestrini wants to merge 1 commit intoPylons:mainfrom
russellballestrini wants to merge 1 commit intoPylons:mainfrom
Conversation
Vendor the minimal subset of pkg_resources into pyramid._pkg_resources (310 lines, from setuptools 80.x, MIT license) to eliminate the setuptools runtime dependency while preserving the asset override architecture (OverrideProvider, register_loader_type, DefaultProvider). Add pyramid.compat_resources as the public API for downstream Pylons packages that participate in the override system. - pyramid._pkg_resources: vendored provider chain - pyramid.compat_resources: public re-exports for ecosystem - setup.py: remove setuptools from install_requires - asset.py, path.py, static.py, config/assets.py: import swaps - docs/conf.py: use importlib.metadata for version detection - tests/test__pkg_resources.py: 67 tests, 97.66% coverage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vendor the minimal subset of pkg_resources into pyramid._pkg_resources (310 lines, from setuptools 80.x, MIT license) to eliminate the setuptools runtime dependency while preserving the asset override architecture (OverrideProvider, register_loader_type, DefaultProvider).
Add pyramid.compat_resources as the public API for downstream Pylons packages that participate in the override system.
Removing the setuptools Runtime Dependency from Pyramid and the Pylons Ecosystem
Problem
setuptools 82.0.0 removed
pkg_resourcesentirely (Pylons/pyramid#3731). Pyramid depends onpkg_resourcesat runtime for its asset resolution system -- asset overrides, static views, template lookups, and the provider chain all route throughpkg_resources.resource_filename,resource_exists,resource_isdir, and theDefaultProvider/OverrideProviderclass hierarchy.This is not a simple
importlib.resources.files()swap. You can't just replacepkg_resourceswithimportlib.resourcesfor packages that participate in Pyramid's asset override system -- doing so would bypass theOverrideProviderchain entirely, potentially breakingconfig.override_asset()silently.Approach
This work was done by fxhp using a machine learning coding agent. The agent audited all 108 Pylons GitHub repos, implemented the vendoring, migrated 30 downstream packages, and ran all test suites. Design decisions and migration strategy were directed by fxhp; the agent executed.
Antti Haapala provided key technical guidance in the Pylons Discord: he identified that the import override system means you cannot simply use
importlib.resources.files().joinpath()and expect it to work without breaking overrides. He triaged the downstream packages -- flagging deform's module-levelresource_filenamecall as the worst pattern (fails at import time), substanced'sEntryPointas needingimportlib.metadata, and weberror'sworking_setas needingimportlib.metadata.distributions(). He also raised concerns about the threadlocal-based override mechanism: it doesn't cooperate with Pyramid's config/request lifecycle, and third-party libraries that cacheresource_filenameresults can produce heisenbugs. His proposed direction: deprecate the old API and require explicit registry/request access for asset resolution.raydeo contributed important perspective on backward compatibility:
importlib.resourceshas a mechanism (the Traversable protocol) similar to whatpkg_resourcesprovided, pointing toward a possible future where Pyramid's override system is rebuilt onimportlib.resourcesrather than vendoredpkg_resources. He emphasized not breaking the override system "in any way" during the transition -- noting that PyPI/Warehouse famously used it to swap UI assets at runtime.PRs #3748 and #3749 (by mmerickel) already migrated
DottedNameResolverand the scripts (pshell,pdistreport) toimportlib.metadata, clearing the path for this work to focus on the remaining hard part: the asset/resource provider system.Solution
Pyramid Core
Vendor the minimal subset of pkg_resources directly into Pyramid, eliminating the setuptools runtime dependency while preserving the asset override architecture.
Two new modules:
pyramid._pkg_resources(310 lines) -- Vendored from setuptools 80.x (MIT license, Jason R. Coombs). Contains only what Pyramid needs:_provider_factories,register_loader_type(),get_provider(),_find_adapter(),ResourceManager,NullProvider,DefaultProvider, and the module-level convenience functions (resource_filename,resource_exists,resource_isdir,resource_stream,resource_string,resource_listdir).pyramid.compat_resources(46 lines) -- Public API for the Pylons ecosystem. Re-exports the functions from_pkg_resourcesso downstream packages have a stable import path. Packages that depend on Pyramid should import from here rather than usingimportlib.resourcesdirectly, to preserve override compatibility.Four existing source files updated (import swap only):
src/pyramid/asset.pyimport pkg_resources→from pyramid import _pkg_resourcessrc/pyramid/path.pyimport pkg_resources→from pyramid import _pkg_resourcessrc/pyramid/static.pyfrom pkg_resources import ...→from pyramid._pkg_resources import ...src/pyramid/config/assets.pyimport pkg_resources→from pyramid import _pkg_resourcessetuptoolsremoved frominstall_requiresinsetup.py. The build-timefrom setuptools import setupstays (guaranteed by PEP 517).New test file:
tests/test__pkg_resources.py-- 67 tests covering unit, integration, and functional scenarios at 97.66% coverage.Total pyramid core tests: 2593 existing + 67 new = 2660, all passing.
Downstream Packages
30 repositories across the Pylons organization were updated. The changes fall into categories based on the dependency graph:
1. Packages that depend on Pyramid (one-liner import swap)
These packages use
pyramid.compat_resources-- Pyramid's public override-aware resource API. This is the right approach because these packages participate in Pyramid's asset override system (e.g., pyramid_chameleon templates can be overridden viaconfig.override_asset()).renderer.py(resource_filename,resource_exists)__init__.py__init__.py__init__.py,zcml.pysrc/pyramid_skins/configuration.pyakhet/static.py(resource_exists)cartouche/registration.pytests/test_units.pyfile/views.py,form/__init__.py2. Packages NOT depending on Pyramid (use stdlib directly)
These packages cannot import from
pyramid.compat_resourcesbecause they don't depend on Pyramid. They useimportlib.resources.files()from the stdlib (Python 3.9+), which is correct because they do not participate in Pyramid's asset override system.deform (critical -- the module-level
resource_filenamecall is the worst pattern because it runs at import time):weberror (two distinct patterns):
evalexception.py:resource_filename→importlib.resources.files()formatter.py:pkg_resources.working_set→importlib.metadata.distributions()3. EntryPoint replacement (substanced)
substanced's
EntryPoint.parse().load()is really just importing a module by dotted name:4. docs/conf.py files (19 repos)
Repos updated: colander, deform, hupper, hypatia, plaster_pastedeploy, pyramid_chameleon, pyramid_cookbook, pyramid_exclog, pyramid_jinja2, pyramid_mailer, pyramid_mako, pyramid_nacl_session, pyramid_retry, pyramid_rpc, pyramid_simpleform, pyramid_tm, pyramid_zcml, pyramid_zodbconn, venusian.
5. Test file fixes
tests/conftest.pypkg_resources.working_set.add_entry()→sys.path.insert()tests/test_it.pypkg_resources.get_distribution/parse_version→importlib.metadata.version+packaging.version.Versiontests/test_units.pypkg_resources.resource_filename→pyramid.compat_resources.resource_filenametests/test_functional.pypkg_resources.get_distribution().location→importlib.resources.files()tests/test_doctests.pypkg_resources.resource_filename→pyramid.compat_resources.resource_filenametests/test_api.pypkg_resources.resource_filename→pyramid.compat_resources.resource_filenametests/test_evolution.pyimport_modulemock targettests/test_template.pypkg_resources.resource_filename→importlib.resources.filesTest Results
Pyramid Core
_pkg_resources.pyimport pyramiddoes NOT triggerimport pkg_resourcesfrom setuptoolsDownstream Packages (full test suites)
Not runnable (pre-existing Python 2 syntax)
UnencryptedCookieSessionFactoryConfig)Defect Found During Testing
NullProvider.__init__crashed whenmodule.__file__isNone(namespace packages). Fixed:getattrreturns the attribute valueNone(not the default'') because__file__exists but is set toNoneon namespace packages.Migration Guide for Third-Party Packages
If your package depends on Pyramid
This preserves compatibility with
config.override_asset().If your package does NOT depend on Pyramid
For docs/conf.py version detection
For EntryPoint.load() patterns
Architectural Notes and Future Direction
This vendoring approach is a pragmatic bridge fix that preserves exact backward compatibility. It does not attempt to redesign the asset override system. The community has been discussing a longer-term architectural direction, and there are important considerations for future work.
Why not just use
importlib.resources.files()everywhere?Pyramid's asset override system works by subclassing
DefaultProviderand callingregister_loader_type()to intercept resource lookups at thepkg_resourcesprovider level. When you callconfig.override_asset('mypackage:templates/', 'otherpackage:templates/'), Pyramid installs anOverrideProviderthat redirects resource lookups transparently.If downstream packages switch to
importlib.resources.files()directly, those calls bypass the override chain entirely. This is why packages that depend on Pyramid usepyramid.compat_resources(which routes through the vendored provider system) rather thanimportlib.resources.Packages that do NOT depend on Pyramid (deform, weberror) can safely use
importlib.resources.files()because they don't participate in the override system.The threadlocal concern
The current override mechanism relies on threadlocals (
get_current_registry()) to find the active registry and its configured overrides. As Antti Haapala pointed out, this creates potential issues:resource_filenameresults on first use may get stale overridesraydeo noted the design is "pretty slick" for transparently overriding resources in libraries like pyramid_chameleon and pyramid_jinja2 that use
pkg_resources.resource_filename, but acknowledged the pitfalls.Possible future directions
Deprecate the threadlocal-based override mechanism in favor of a new API that requires explicit registry/request access (e.g.,
request.resolve_asset('mypackage:templates/foo.pt')orconfig.resolve_asset(...)). This is the direction Antti Haapala advocated: a cleaner API where callers must have access to the registry or request, avoiding heisenbugs from cached resource lookups.Build override support on
importlib.resourcesusing its Traversable protocol and custom resource readers. As raydeo noted, this mechanism is "more flexible than pkg_resources" and provides a more modern hook point thanregister_loader_type(). raydeo's optimistic goal: Pyramid patchesimportlib.resources(notpkg_resources) for overrides, so the system works natively without setuptools.Keep vendored
_pkg_resourcesas a fallback for the transition period, allowing packages to migrate at their own pace. raydeo emphasized not breaking the override system "in any way" during the transition.The vendored module gives the ecosystem breathing room to plan this transition without being forced by a setuptools release.
What's NOT Included
importlib.metadatain PRs remove pkg_resources from DottedNameResolver #3748/Remove pkg resources from scripts #3749has_metadata,get_metadata) -- not used by Pyramid's asset systemFiles Summary
_pkg_resources.py,compat_resources.py,test__pkg_resources.py)asset.py,path.py,static.py,config/assets.py,setup.py)