Skip to content

Commit

Permalink
Fixed #35252 -- Optimized _route_to_regex().
Browse files Browse the repository at this point in the history
co-authored-by: Nick Pope <[email protected]>
  • Loading branch information
2 people authored and felixxm committed Mar 5, 2024
1 parent 241adf6 commit eff21d8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 33 deletions.
8 changes: 4 additions & 4 deletions django/urls/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ def register_converter(converter, type_name):
REGISTERED_CONVERTERS[type_name] = converter()
get_converters.cache_clear()

from django.urls.resolvers import _route_to_regex

_route_to_regex.cache_clear()


@functools.cache
def get_converters():
return {**DEFAULT_CONVERTERS, **REGISTERED_CONVERTERS}


def get_converter(raw_converter):
return get_converters()[raw_converter]
46 changes: 23 additions & 23 deletions django/urls/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from django.utils.regex_helper import _lazy_re_compile, normalize
from django.utils.translation import get_language

from .converters import get_converter
from .converters import get_converters
from .exceptions import NoReverseMatch, Resolver404
from .utils import get_callable

Expand Down Expand Up @@ -243,48 +243,48 @@ def __str__(self):
r"<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>"
)

whitespace_set = frozenset(string.whitespace)


@functools.lru_cache
def _route_to_regex(route, is_endpoint):
"""
Convert a path pattern into a regular expression. Return the regular
expression and a dictionary mapping the capture names to the converters.
For example, 'foo/<int:pk>' returns '^foo\\/(?P<pk>[0-9]+)'
and {'pk': <django.urls.converters.IntConverter>}.
"""
original_route = route
parts = ["^"]
all_converters = get_converters()
converters = {}
while True:
match = _PATH_PARAMETER_COMPONENT_RE.search(route)
if not match:
parts.append(re.escape(route))
break
elif not set(match.group()).isdisjoint(string.whitespace):
previous_end = 0
for match_ in _PATH_PARAMETER_COMPONENT_RE.finditer(route):
if not whitespace_set.isdisjoint(match_[0]):
raise ImproperlyConfigured(
"URL route '%s' cannot contain whitespace in angle brackets "
"<…>." % original_route
f"URL route {route!r} cannot contain whitespace in angle brackets <…>."
)
parts.append(re.escape(route[: match.start()]))
route = route[match.end() :]
parameter = match["parameter"]
# Default to make converter "str" if unspecified (parameter always
# matches something).
raw_converter, parameter = match_.groups(default="str")
if not parameter.isidentifier():
raise ImproperlyConfigured(
"URL route '%s' uses parameter name %r which isn't a valid "
"Python identifier." % (original_route, parameter)
f"URL route {route!r} uses parameter name {parameter!r} which "
"isn't a valid Python identifier."
)
raw_converter = match["converter"]
if raw_converter is None:
# If a converter isn't specified, the default is `str`.
raw_converter = "str"
try:
converter = get_converter(raw_converter)
converter = all_converters[raw_converter]
except KeyError as e:
raise ImproperlyConfigured(
"URL route %r uses invalid converter %r."
% (original_route, raw_converter)
f"URL route {route!r} uses invalid converter {raw_converter!r}."
) from e
converters[parameter] = converter
parts.append("(?P<" + parameter + ">" + converter.regex + ")")

start, end = match_.span()
parts.append(re.escape(route[previous_end:start]))
previous_end = end
parts.append(f"(?P<{parameter}>{converter.regex})")

parts.append(re.escape(route[previous_end:]))
if is_endpoint:
parts.append(r"\Z")
return "".join(parts), converters
Expand Down
3 changes: 3 additions & 0 deletions docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ Miscellaneous
:py:class:`html.parser.HTMLParser` subclasses. This results in a more robust
and faster operation, but there may be small differences in the output.

* The undocumented ``django.urls.converters.get_converter()`` function is
removed.

.. _deprecated-features-5.1:

Features deprecated in 5.1
Expand Down
10 changes: 4 additions & 6 deletions tests/urlpatterns/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,12 @@ class EmptyCBV(View):
path("foo", EmptyCBV())

def test_whitespace_in_route(self):
msg = (
"URL route 'space/<int:num>/extra/<str:%stest>' cannot contain "
"whitespace in angle brackets <…>"
)
msg = "URL route %r cannot contain whitespace in angle brackets <…>"
for whitespace in string.whitespace:
with self.subTest(repr(whitespace)):
with self.assertRaisesMessage(ImproperlyConfigured, msg % whitespace):
path("space/<int:num>/extra/<str:%stest>" % whitespace, empty_view)
route = "space/<int:num>/extra/<str:%stest>" % whitespace
with self.assertRaisesMessage(ImproperlyConfigured, msg % route):
path(route, empty_view)
# Whitespaces are valid in paths.
p = path("space%s/<int:num>/" % string.whitespace, empty_view)
match = p.resolve("space%s/1/" % string.whitespace)
Expand Down

0 comments on commit eff21d8

Please sign in to comment.