ソースを参照

Fixed #35252 -- Optimized _route_to_regex().

co-authored-by: Nick Pope <nick@nickpope.me.uk>
Adam Johnson 1 年間 前
コミット
eff21d8e7a
4 ファイル変更34 行追加33 行削除
  1. 4 4
      django/urls/converters.py
  2. 23 23
      django/urls/resolvers.py
  3. 3 0
      docs/releases/5.1.txt
  4. 4 6
      tests/urlpatterns/tests.py

+ 4 - 4
django/urls/converters.py

@@ -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]

+ 23 - 23
django/urls/resolvers.py

@@ -26,7 +26,7 @@ from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
 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
 
@@ -243,7 +243,10 @@ _PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile(
     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
@@ -251,40 +254,37 @@ def _route_to_regex(route, is_endpoint):
     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

+ 3 - 0
docs/releases/5.1.txt

@@ -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

+ 4 - 6
tests/urlpatterns/tests.py

@@ -246,14 +246,12 @@ class SimplifiedURLTests(SimpleTestCase):
             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)