Dynamic routes with percent-encodable fixed segments (space, non-ASCII) are unreachable and url_for( - #13498
Conversation
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously requested API documentation and changelog attribution are now present and accurately cover the changed metadata contracts. Reviews (4): Last reviewed commit: "Document the route metadata change" | Re-trigger Greptile |
| @property | ||
| def canonical(self) -> str: | ||
| return self._formatter | ||
| return self._canonical |
There was a problem hiding this comment.
DynamicResource.canonical, PrefixResource.canonical, and StaticResource.get_info()["prefix"] now expose raw spaces and non-ASCII characters instead of percent-encoded values. This user-visible representation change needs the repository-required API documentation, and its changelog fragment needs the required -- by :user: attribution.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13498 +/- ##
=======================================
Coverage 99.00% 99.00%
=======================================
Files 132 132
Lines 49635 49696 +61
Branches 2575 2575
=======================================
+ Hits 49141 49202 +61
Misses 370 370
Partials 124 124
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will not alter performance
Comparing Footnotes
|
bfcccb4 to
c331f57
Compare
What do these changes do?
DynamicResourceandPrefixResourcepercent-encoded their fixed path segment atregistration and used that encoded form for both the match pattern and
canonical,while
UrlDispatcher.resolvewalks the decodedpath_safeand derives index keysfrom
canonical— so those resources were never probed and always returned404.The fix keeps the encoded form only for URL construction (
_formatter, and a new_quoted_prefixonPrefixResource) and uses the as-written form for matching andcanonical, which is the representationPlainResourcealready used and the routeralready matches on.
_get_resource_index_keyandresolveare unchanged.Are there changes in behavior for the user?
Yes, two.
1. Routes that were silently unreachable now resolve. Before this change a dynamic
route
/hello world/{name}, a static prefix/static files, and a sub-applicationmounted at
/sub appreturned404for every request. They now match, and the URLbuilt by
url_for()routes back to its own resource.2. Route metadata reports the registered form instead of the percent-encoded one:
DynamicResource.canonical/hello%20world/{name}/hello world/{name}PrefixResource.canonical(static, sub-app)/static%20files/static filesget_info()['prefix']/static%20files/static filesDynamicResource.get_info()['formatter']/hello%20world/{name}formatterdeliberately stays percent-encoded — it is whaturl_for()builds from.Third-party instrumentation that labels routes by
resource.canonical(OpenTelemetry,Prometheus) will observe the new representation. This is documented under each affected
attribute in
docs/web_reference.rstand called out in the change note.Is it a substantial burden for the maintainers to support this?
No new public API and no new configuration. It removes an inconsistency rather than
adding one: matching and
canonicalnow consistently use the as-written representationwhile URL building consistently uses the percent-encoded one, instead of mixing the two.
The single new attribute,
PrefixResource._quoted_prefix, is private.Related issue number
Fixes #13433
Checklist
CONTRIBUTORS.txtCHANGES/folderNotes for reviewers
versionchangeddirective version is a guess.docs/web_reference.rstuses.. versionchanged:: 3.14.4on the assumption that this.bugfixgets backported tothe 3.14 line, making 3.14.4 the earliest release containing it. If you label it
minor-only, it should read
3.15— onesedeither way, just say which.Known residual gap, matching existing behaviour. A fixed part containing a literal
%(e.g./100%/{x}) is still unmatchable, becausepath_safeleaves%25encoded.This matches
PlainResource's existing limitation and preserves_requote_path's"existing %-sequences are pre-encodings" semantics, so
/a%2Fb/{x}keeps working.Pre-existing and deliberately untouched:
PlainResource.url_for()returns a rawspace for a path registered as
/hello world(it builds withencoded=Truefrom theas-written path). Verified identical on
masterand on this branch, so it is not aregression from this PR and is left out of scope.
The
Backport label addedcheck is red and stays red until a maintainer applies abackport-*label — nothing in this diff can turn it green.Verification run
Run against the rebased branch with the C extensions built (
http_parser.NO_EXTENSIONSis
False), CPython 3.14.4 on macOS.The two new tests in
tests/test_web_urldispatcher.pywere confirmed to fail onmasterand pass on this branch:Sub-application resolution, branch vs
master:Lint:
black,isort,pyupgrade,codespell,flake8(7.3.0 with the pinnedplugins) and the changelog hooks all pass on the changed files.
mypyreports onlypre-existing errors unrelated to this diff (
aiohttp/worker.py, and unusedtype: ignores atweb_urldispatcher.py:161,169outside every hunk here).Drafted with Claude Code (Claude Opus 5); pending review by @loonister1.