From 832cae47693b4c07a1fa826dce13b9af7a91ebaf Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 24 Aug 2025 21:35:04 -0700 Subject: [PATCH 1/5] Add `get_spec` to asset sources. --- src/pyramid/config/assets.py | 8 ++++++++ tests/test_config/test_assets.py | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/pyramid/config/assets.py b/src/pyramid/config/assets.py index 6f2ddbe4aa..2838db1e5f 100644 --- a/src/pyramid/config/assets.py +++ b/src/pyramid/config/assets.py @@ -223,6 +223,11 @@ def __init__(self, package, prefix): def get_path(self, resource_name): return f'{self.prefix}{resource_name}' + def get_spec(self, resource_name): + path = self.get_path(resource_name) + if pkg_resources.resource_exists(self.pkg_name, path): + return f'{self.pkg_name}:{path}' + def get_filename(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): @@ -270,6 +275,9 @@ def get_path(self, resource_name): path = self.prefix return path + def get_spec(self, resource_name): + return self.get_filename(resource_name) + def get_filename(self, resource_name): path = self.get_path(resource_name) if os.path.exists(path): diff --git a/tests/test_config/test_assets.py b/tests/test_config/test_assets.py index 1d2cfcd5c4..5c535ec6c6 100644 --- a/tests/test_config/test_assets.py +++ b/tests/test_config/test_assets.py @@ -916,6 +916,24 @@ def _makeOne(self, prefix, package='tests.test_config'): klass = self._getTargetClass() return klass(package, prefix) + def test_get_spec(self): + source = self._makeOne('') + self.assertEqual( + source.get_spec('test_assets.py'), + 'tests.test_config:test_assets.py', + ) + + def test_get_spec_with_prefix(self): + source = self._makeOne('test_assets.py') + self.assertEqual( + source.get_spec(''), + 'tests.test_config:test_assets.py', + ) + + def test_get_spec_file_doesnt_exist(self): + source = self._makeOne('') + self.assertIsNone(source.get_spec('wont_exist')) + class TestFSAssetSource(AssetSourceIntegrationTests, unittest.TestCase): def _getTargetClass(self): @@ -927,6 +945,23 @@ def _makeOne(self, prefix, base_prefix=here): klass = self._getTargetClass() return klass(os.path.join(base_prefix, prefix)) + def test_get_spec(self): + source = self._makeOne('') + self.assertEqual( + source.get_spec('test_assets.py'), + os.path.join(here, 'test_assets.py'), + ) + + def test_get_spec_with_prefix(self): + source = self._makeOne('test_assets.py') + self.assertEqual( + source.get_spec(''), os.path.join(here, 'test_assets.py') + ) + + def test_get_spec_file_doesnt_exist(self): + source = self._makeOne('') + self.assertEqual(source.get_spec('wont_exist'), None) + class TestDirectoryOverride(unittest.TestCase): def _getTargetClass(self): From 9b9ac95c7207a5b0a5c2fd7ef56977e9b285f206 Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 24 Aug 2025 21:38:56 -0700 Subject: [PATCH 2/5] Add `get_spec` to `PackageOverrides` --- src/pyramid/config/assets.py | 6 ++++++ tests/test_config/test_assets.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/pyramid/config/assets.py b/src/pyramid/config/assets.py index 2838db1e5f..3c6a8d3604 100644 --- a/src/pyramid/config/assets.py +++ b/src/pyramid/config/assets.py @@ -122,6 +122,12 @@ def filtered_sources(self, resource_name): if o is not None: yield o + def get_spec(self, resource_name): + for source, path in self.filtered_sources(resource_name): + result = source.get_spec(path) + if result is not None: + return result + def get_filename(self, resource_name): for source, path in self.filtered_sources(resource_name): result = source.get_filename(path) diff --git a/tests/test_config/test_assets.py b/tests/test_config/test_assets.py index 5c535ec6c6..ea2fff7414 100644 --- a/tests/test_config/test_assets.py +++ b/tests/test_config/test_assets.py @@ -638,6 +638,28 @@ def test_filtered_sources(self): po.overrides = overrides self.assertEqual(list(po.filtered_sources('whatever')), ['foo']) + def test_get_spec(self): + source = DummyAssetSource(spec='test:foo.pt') + overrides = [DummyOverride(None), DummyOverride((source, ''))] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides = overrides + result = po.get_spec('whatever') + self.assertEqual(result, 'test:foo.pt') + self.assertEqual(source.resource_name, '') + + def test_get_spec_file_doesnt_exist(self): + source = DummyAssetSource(spec=None) + overrides = [ + DummyOverride(None), + DummyOverride((source, 'wont_exist')), + ] + package = DummyPackage('package') + po = self._makeOne(package) + po.overrides = overrides + self.assertEqual(po.get_spec('whatever'), None) + self.assertEqual(source.resource_name, 'wont_exist') + def test_get_filename(self): source = DummyAssetSource(filename='foo.pt') overrides = [DummyOverride(None), DummyOverride((source, ''))] @@ -1053,6 +1075,10 @@ class DummyAssetSource: def __init__(self, **kw): self.kw = kw + def get_spec(self, resource_name): + self.resource_name = resource_name + return self.kw['spec'] + def get_filename(self, resource_name): self.resource_name = resource_name return self.kw['filename'] From ed315c142c7f66f6ee9ff24f4bad6997b0b45bfb Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 24 Aug 2025 21:42:18 -0700 Subject: [PATCH 3/5] Add `get_spec` to `IPackageOverrides` --- src/pyramid/interfaces.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 4ee2941894..063b0385ee 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1079,6 +1079,14 @@ def get_filename(fullname): class IPackageOverrides(IPEP302Loader): """Utility for pkg_resources overrides""" + def get_spec(resource_name): + """Return a specifier for the resource. + + The specifier may be a dotted Python name or an absolute path on the + filesystem. + + """ + # VH_ROOT_KEY is an interface; its imported from other packages (e.g. # traversalwrapper) From eded8c0293b19f5b5f214fca91ba4152d63fce0d Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 24 Aug 2025 22:01:27 -0700 Subject: [PATCH 4/5] Update tests. --- tests/test_config/pkgs/cachebust/__init__.py | 0 .../pkgs/cachebust/override/foo.png | Bin 0 -> 72 bytes tests/test_config/pkgs/cachebust/path/foo.png | Bin 0 -> 72 bytes tests/test_config/test_views.py | 25 +++++++++++++----- 4 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 tests/test_config/pkgs/cachebust/__init__.py create mode 100644 tests/test_config/pkgs/cachebust/override/foo.png create mode 100644 tests/test_config/pkgs/cachebust/path/foo.png diff --git a/tests/test_config/pkgs/cachebust/__init__.py b/tests/test_config/pkgs/cachebust/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_config/pkgs/cachebust/override/foo.png b/tests/test_config/pkgs/cachebust/override/foo.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc2f763c0edb3d22fb88b42fb9a19bf64cff940 GIT binary patch literal 72 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ry{GKk3Ar*{_EDXQ@GxRpvF}-H= U)!h6_1t`Vf>FVdQ&MBb@0NyzeE&u=k literal 0 HcmV?d00001 diff --git a/tests/test_config/pkgs/cachebust/path/foo.png b/tests/test_config/pkgs/cachebust/path/foo.png new file mode 100644 index 0000000000000000000000000000000000000000..43440f881fa8c8874aa30b39733f14b3a0cc6531 GIT binary patch literal 72 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ry{GKk3Ar*{_EDXQ@Gfd*0&s5B4 Te^GiaNQ%MJ)z4*}Q$iB}*{2W} literal 0 HcmV?d00001 diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index 2018e61f29..ce7038b5d6 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -4065,9 +4065,12 @@ def test_generate_url_cachebust_with_overrides(self): config = testing.setUp() try: request = testing.DummyRequest() - config.add_static_view('static', 'path') + config.add_static_view( + 'static', 'tests.test_config.pkgs.cachebust:path/' + ) config.override_asset( - 'tests.test_config:path/', 'tests.test_config:other_path/' + 'tests.test_config.pkgs.cachebust:path/', + 'tests.test_config.pkgs.cachebust:override/', ) def cb(val): @@ -4077,11 +4080,21 @@ def cb_(request, subpath, kw): return cb_ - config.add_cache_buster('path', cb('foo')) - result = request.static_url('path/foo.png') + config.add_cache_buster( + 'tests.test_config.pkgs.cachebust:path/', cb('foo') + ) + result = request.static_url( + 'tests.test_config.pkgs.cachebust:path/foo.png' + ) self.assertEqual(result, 'http://example.com/static/foo.png?x=foo') - config.add_cache_buster('other_path', cb('bar'), explicit=True) - result = request.static_url('path/foo.png') + config.add_cache_buster( + 'tests.test_config.pkgs.cachebust:override/', + cb('bar'), + explicit=True, + ) + result = request.static_url( + 'tests.test_config.pkgs.cachebust:path/foo.png' + ) self.assertEqual(result, 'http://example.com/static/foo.png?x=bar') finally: testing.tearDown() From 73593a0d318121662de1ec56bfa838a390d45f96 Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 24 Aug 2025 22:01:32 -0700 Subject: [PATCH 5/5] Update implementation. --- src/pyramid/config/views.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index fababf542f..302704c3e8 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -2338,14 +2338,7 @@ def _bust_asset_path(self, request, spec, subpath, kw): pathspec = f'{pkg_name}:{pkg_subpath}{subpath}' overrides = registry.queryUtility(IPackageOverrides, name=pkg_name) if overrides is not None: - resource_name = posixpath.join(pkg_subpath, subpath) - sources = overrides.filtered_sources(resource_name) - for source, filtered_path in sources: - rawspec = source.get_path(filtered_path) - if hasattr(source, 'pkg_name'): - rawspec = f'{source.pkg_name}:{rawspec}' - break - + rawspec = overrides.get_spec(f'{pkg_subpath}{subpath}') else: pathspec = pkg_subpath + subpath @@ -2354,6 +2347,7 @@ def _bust_asset_path(self, request, spec, subpath, kw): kw['pathspec'] = pathspec kw['rawspec'] = rawspec + print(kw) for spec_, cachebust, explicit in reversed(self.cache_busters): if (explicit and rawspec.startswith(spec_)) or ( not explicit and pathspec.startswith(spec_)