# Django 5.2 documentation

# Source code for django.urls.resolvers

    """
    This module converts requested URLs to callback view functions.

    URLResolver is the main class here. Its resolve() method takes a URL (as
    a string) and returns a ResolverMatch object which provides access to all
    attributes of the resolved URL match.
    """

    import functools
    import inspect
    import re
    import string
    from importlib import import_module
    from pickle import PicklingError
    from urllib.parse import quote

    from asgiref.local import Local

    from django.conf import settings
    from django.core.checks import Error, Warning
    from django.core.checks.urls import check_resolver
    from django.core.exceptions import ImproperlyConfigured
    from django.utils.datastructures import MultiValueDict
    from django.utils.functional import cached_property
    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_converters
    from .exceptions import NoReverseMatch, Resolver404
    from .utils import get_callable

    class ResolverMatch:
        def __init__(
            self,
            func,
            args,
            kwargs,
            url_name=None,
            app_names=None,
            namespaces=None,
            route=None,
            tried=None,
            captured_kwargs=None,
            extra_kwargs=None,
        ):
            self.func = func
            self.args = args
            self.kwargs = kwargs
            self.url_name = url_name
            self.route = route
            self.tried = tried
            self.captured_kwargs = captured_kwargs
            self.extra_kwargs = extra_kwargs

            # If a URLRegexResolver doesn't have a namespace or app_name, it passes
            # in an empty value.
            self.app_names = [x for x in app_names if x] if app_names else []
            self.app_name = ":".join(self.app_names)
            self.namespaces = [x for x in namespaces if x] if namespaces else []
            self.namespace = ":".join(self.namespaces)

            if hasattr(func, "view_class"):
                func = func.view_class
            if not hasattr(func, "__name__"):
                # A class-based view
                self._func_path = func.__class__.__module__ + "." + func.__class__.__name__
            else:
                # A function-based view
                self._func_path = func.__module__ + "." + func.__name__

            view_path = url_name or self._func_path
            self.view_name = ":".join(self.namespaces + [view_path])

        def __getitem__(self, index):
            return (self.func, self.args, self.kwargs)[index]

        def __repr__(self):
            if isinstance(self.func, functools.partial):
                func = repr(self.func)
            else:
                func = self._func_path
            return (
                "ResolverMatch(func=%s, args=%r, kwargs=%r, url_name=%r, "
                "app_names=%r, namespaces=%r, route=%r%s%s)"
                % (
                    func,
                    self.args,
                    self.kwargs,
                    self.url_name,
                    self.app_names,
                    self.namespaces,
                    self.route,
                    (
                        f", captured_kwargs={self.captured_kwargs!r}"
                        if self.captured_kwargs
                        else ""
                    ),
                    f", extra_kwargs={self.extra_kwargs!r}" if self.extra_kwargs else "",
                )
            )

        def __reduce_ex__(self, protocol):
            raise PicklingError(f"Cannot pickle {self.__class__.__qualname__}.")

    def get_resolver(urlconf=None):
        if urlconf is None:
            urlconf = settings.ROOT_URLCONF
        return _get_cached_resolver(urlconf)

    @functools.cache
    def _get_cached_resolver(urlconf=None):
        return URLResolver(RegexPattern(r"^/"), urlconf)

    @functools.cache
    def get_ns_resolver(ns_pattern, resolver, converters):
        # Build a namespaced resolver for the given parent URLconf pattern.
        # This makes it possible to have captured parameters in the parent
        # URLconf pattern.
        pattern = RegexPattern(ns_pattern)
        pattern.converters = dict(converters)
        ns_resolver = URLResolver(pattern, resolver.url_patterns)
        return URLResolver(RegexPattern(r"^/"), [ns_resolver])

    class LocaleRegexDescriptor:
        def __get__(self, instance, cls=None):
            """
            Return a compiled regular expression based on the active language.
            """
            if instance is None:
                return self
            # As a performance optimization, if the given regex string is a regular
            # string (not a lazily-translated string proxy), compile it once and
            # avoid per-language compilation.
            pattern = instance._regex
            if isinstance(pattern, str):
                instance.__dict__["regex"] = self._compile(pattern)
                return instance.__dict__["regex"]
            language_code = get_language()
            if language_code not in instance._regex_dict:
                instance._regex_dict[language_code] = self._compile(str(pattern))
            return instance._regex_dict[language_code]

        def _compile(self, regex):
            try:
                return re.compile(regex)
            except re.error as e:
                raise ImproperlyConfigured(
                    f'"{regex}" is not a valid regular expression: {e}'
                ) from e

    class CheckURLMixin:
        def describe(self):
            """
            Format the URL pattern for display in warning messages.
            """
            description = "'{}'".format(self)
            if self.name:
                description += " [name='{}']".format(self.name)
            return description

        def _check_pattern_startswith_slash(self):
            """
            Check that the pattern does not begin with a forward slash.
            """
            if not settings.APPEND_SLASH:
                # Skip check as it can be useful to start a URL pattern with a slash
                # when APPEND_SLASH=False.
                return []
            if self._regex.startswith(("/", "^/", "^\\/")) and not self._regex.endswith(
                "/"
            ):
                warning = Warning(
                    "Your URL pattern {} has a route beginning with a '/'. Remove this "
                    "slash as it is unnecessary. If this pattern is targeted in an "
                    "include(), ensure the include() pattern has a trailing '/'.".format(
                        self.describe()
                    ),
                    id="urls.W002",
                )
                return [warning]
            else:
                return []

    class RegexPattern(CheckURLMixin):
        regex = LocaleRegexDescriptor()

        def __init__(self, regex, name=None, is_endpoint=False):
            self._regex = regex
            self._regex_dict = {}
            self._is_endpoint = is_endpoint
            self.name = name
            self.converters = {}

        def match(self, path):
            match = (
                self.regex.fullmatch(path)
                if self._is_endpoint and self.regex.pattern.endswith("$")
                else self.regex.search(path)
            )
            if match:
                # If there are any named groups, use those as kwargs, ignoring
                # non-named groups. Otherwise, pass all non-named arguments as
                # positional arguments.
                kwargs = match.groupdict()
                args = () if kwargs else match.groups()
                kwargs = {k: v for k, v in kwargs.items() if v is not None}
                return path[match.end() :], args, kwargs
            return None

        def check(self):
            warnings = []
            warnings.extend(self._check_pattern_startswith_slash())
            if not self._is_endpoint:
                warnings.extend(self._check_include_trailing_dollar())
            return warnings

        def _check_include_trailing_dollar(self):
            if self._regex.endswith("$") and not self._regex.endswith(r"\$"):
                return [
                    Warning(
                        "Your URL pattern {} uses include with a route ending with a '$'. "
                        "Remove the dollar from the route to avoid problems including "
                        "URLs.".format(self.describe()),
                        id="urls.W001",
                    )
                ]
            else:
                return []

        def __str__(self):
            return str(self._regex)

    _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
        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>}.
        """
        parts = ["^"]
        all_converters = get_converters()
        converters = {}
        previous_end = 0
        for match_ in _PATH_PARAMETER_COMPONENT_RE.finditer(route):
            if not whitespace_set.isdisjoint(match_[0]):
                raise ImproperlyConfigured(
                    f"URL route {route!r} cannot contain whitespace in angle brackets <…>."
                )
            # Default to make converter "str" if unspecified (parameter always
            # matches something).
            raw_converter, parameter = match_.groups(default="str")
            if not parameter.isidentifier():
                raise ImproperlyConfigured(
                    f"URL route {route!r} uses parameter name {parameter!r} which "
                    "isn't a valid Python identifier."
                )
            try:
                converter = all_converters[raw_converter]
            except KeyError as e:
                raise ImproperlyConfigured(
                    f"URL route {route!r} uses invalid converter {raw_converter!r}."
                ) from e
            converters[parameter] = converter

            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

    class LocaleRegexRouteDescriptor:
        def __get__(self, instance, cls=None):
            """
            Return a compiled regular expression based on the active language.
            """
            if instance is None:
                return self
            # As a performance optimization, if the given route is a regular string
            # (not a lazily-translated string proxy), compile it once and avoid
            # per-language compilation.
            if isinstance(instance._route, str):
                instance.__dict__["regex"] = re.compile(instance._regex)
                return instance.__dict__["regex"]
            language_code = get_language()
            if language_code not in instance._regex_dict:
                instance._regex_dict[language_code] = re.compile(
                    _route_to_regex(str(instance._route), instance._is_endpoint)[0]
                )
            return instance._regex_dict[language_code]

    class RoutePattern(CheckURLMixin):
        regex = LocaleRegexRouteDescriptor()

        def __init__(self, route, name=None, is_endpoint=False):
            self._route = route
            self._regex, self.converters = _route_to_regex(str(route), is_endpoint)
            self._regex_dict = {}
            self._is_endpoint = is_endpoint
            self.name = name

        def match(self, path):
            match = self.regex.search(path)
            if match:
                # RoutePattern doesn't allow non-named groups so args are ignored.
                kwargs = match.groupdict()
                for key, value in kwargs.items():
                    converter = self.converters[key]
                    try:
                        kwargs[key] = converter.to_python(value)
                    except ValueError:
                        return None
                return path[match.end() :], (), kwargs
            return None

        def check(self):
            warnings = [
                *self._check_pattern_startswith_slash(),
                *self._check_pattern_unmatched_angle_brackets(),
            ]
            route = self._route
            if "(?P<" in route or route.startswith("^") or route.endswith("$"):
                warnings.append(
                    Warning(
                        "Your URL pattern {} has a route that contains '(?P<', begins "
                        "with a '^', or ends with a '$'. This was likely an oversight "
                        "when migrating to django.urls.path().".format(self.describe()),
                        id="2_0.W001",
                    )
                )
            return warnings

        def _check_pattern_unmatched_angle_brackets(self):
            warnings = []
            msg = "Your URL pattern %s has an unmatched '%s' bracket."
            brackets = re.findall(r"[<>]", str(self._route))
            open_bracket_counter = 0
            for bracket in brackets:
                if bracket == "<":
                    open_bracket_counter += 1
                elif bracket == ">":
                    open_bracket_counter -= 1
                    if open_bracket_counter < 0:
                        warnings.append(
                            Warning(msg % (self.describe(), ">"), id="urls.W010")
                        )
                        open_bracket_counter = 0
            if open_bracket_counter > 0:
                warnings.append(Warning(msg % (self.describe(), "<"), id="urls.W010"))
            return warnings

        def __str__(self):
            return str(self._route)

    class LocalePrefixPattern:
        def __init__(self, prefix_default_language=True):
            self.prefix_default_language = prefix_default_language
            self.converters = {}

        @property
        def regex(self):
            # This is only used by reverse() and cached in _reverse_dict.
            return re.compile(re.escape(self.language_prefix))

        @property
        def language_prefix(self):
            language_code = get_language() or settings.LANGUAGE_CODE
            if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
                return ""
            else:
                return "%s/" % language_code

        def match(self, path):
            language_prefix = self.language_prefix
            if path.startswith(language_prefix):
                return path.removeprefix(language_prefix), (), {}
            return None

        def check(self):
            return []

        def describe(self):
            return "'{}'".format(self)

        def __str__(self):
            return self.language_prefix

    class URLPattern:
        def __init__(self, pattern, callback, default_args=None, name=None):
            self.pattern = pattern
            self.callback = callback  # the view
            self.default_args = default_args or {}
            self.name = name

        def __repr__(self):
            return "<%s %s>" % (self.__class__.__name__, self.pattern.describe())

        def check(self):
            warnings = self._check_pattern_name()
            warnings.extend(self.pattern.check())
            warnings.extend(self._check_callback())
            return warnings

        def _check_pattern_name(self):
            """
            Check that the pattern name does not contain a colon.
            """
            if self.pattern.name is not None and ":" in self.pattern.name:
                warning = Warning(
                    "Your URL pattern {} has a name including a ':'. Remove the colon, to "
                    "avoid ambiguous namespace references.".format(self.pattern.describe()),
                    id="urls.W003",
                )
                return [warning]
            else:
                return []

        def _check_callback(self):
            from django.views import View

            view = self.callback
            if inspect.isclass(view) and issubclass(view, View):
                return [
                    Error(
                        "Your URL pattern %s has an invalid view, pass %s.as_view() "
                        "instead of %s."
                        % (
                            self.pattern.describe(),
                            view.__name__,
                            view.__name__,
                        ),
                        id="urls.E009",
                    )
                ]
            return []

        def resolve(self, path):
            match = self.pattern.match(path)
            if match:
                new_path, args, captured_kwargs = match
                # Pass any default args as **kwargs.
                kwargs = {**captured_kwargs, **self.default_args}
                return ResolverMatch(
                    self.callback,
                    args,
                    kwargs,
                    self.pattern.name,
                    route=str(self.pattern),
                    captured_kwargs=captured_kwargs,
                    extra_kwargs=self.default_args,
                )

        @cached_property
        def lookup_str(self):
            """
            A string that identifies the view (e.g. 'path.to.view_function' or
            'path.to.ClassBasedView').
            """
            callback = self.callback
            if isinstance(callback, functools.partial):
                callback = callback.func
            if hasattr(callback, "view_class"):
                callback = callback.view_class
            elif not hasattr(callback, "__name__"):
                return callback.__module__ + "." + callback.__class__.__name__
            return callback.__module__ + "." + callback.__qualname__

    class URLResolver:
        def __init__(
            self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None
        ):
            self.pattern = pattern
            # urlconf_name is the dotted Python path to the module defining
            # urlpatterns. It may also be an object with an urlpatterns attribute
            # or urlpatterns itself.
            self.urlconf_name = urlconf_name
            self.callback = None
            self.default_kwargs = default_kwargs or {}
            self.namespace = namespace
            self.app_name = app_name
            self._reverse_dict = {}
            self._namespace_dict = {}
            self._app_dict = {}
            # set of dotted paths to all functions and classes that are used in
            # urlpatterns
            self._callback_strs = set()
            self._populated = False
            self._local = Local()

        def __repr__(self):
            if isinstance(self.urlconf_name, list) and self.urlconf_name:
                # Don't bother to output the whole list, it can be huge
                urlconf_repr = "<%s list>" % self.urlconf_name[0].__class__.__name__
            else:
                urlconf_repr = repr(self.urlconf_name)
            return "<%s %s (%s:%s) %s>" % (
                self.__class__.__name__,
                urlconf_repr,
                self.app_name,
                self.namespace,
                self.pattern.describe(),
            )

        def check(self):
            messages = []
            for pattern in self.url_patterns:
                messages.extend(check_resolver(pattern))
            return messages or self.pattern.check()

        def _populate(self):
            # Short-circuit if called recursively in this thread to prevent
            # infinite recursion. Concurrent threads may call this at the same
            # time and will need to continue, so set 'populating' on a
            # thread-local variable.
            if getattr(self._local, "populating", False):
                return
            try:
                self._local.populating = True
                lookups = MultiValueDict()
                namespaces = {}
                apps = {}
                language_code = get_language()
                for url_pattern in reversed(self.url_patterns):
                    p_pattern = url_pattern.pattern.regex.pattern
                    p_pattern = p_pattern.removeprefix("^")
                    if isinstance(url_pattern, URLPattern):
                        self._callback_strs.add(url_pattern.lookup_str)
                        bits = normalize(url_pattern.pattern.regex.pattern)
                        lookups.appendlist(
                            url_pattern.callback,
                            (
                                bits,
                                p_pattern,
                                url_pattern.default_args,
                                url_pattern.pattern.converters,
                            ),
                        )
                        if url_pattern.name is not None:
                            lookups.appendlist(
                                url_pattern.name,
                                (
                                    bits,
                                    p_pattern,
                                    url_pattern.default_args,
                                    url_pattern.pattern.converters,
                                ),
                            )
                    else:  # url_pattern is a URLResolver.
                        url_pattern._populate()
                        if url_pattern.app_name:
                            apps.setdefault(url_pattern.app_name, []).append(
                                url_pattern.namespace
                            )
                            namespaces[url_pattern.namespace] = (p_pattern, url_pattern)
                        else:
                            for name in url_pattern.reverse_dict:
                                for (
                                    matches,
                                    pat,
                                    defaults,
                                    converters,
                                ) in url_pattern.reverse_dict.getlist(name):
                                    new_matches = normalize(p_pattern + pat)
                                    lookups.appendlist(
                                        name,
                                        (
                                            new_matches,
                                            p_pattern + pat,
                                            {**defaults, **url_pattern.default_kwargs},
                                            {
                                                **self.pattern.converters,
                                                **url_pattern.pattern.converters,
                                                **converters,
                                            },
                                        ),
                                    )
                            for namespace, (
                                prefix,
                                sub_pattern,
                            ) in url_pattern.namespace_dict.items():
                                current_converters = url_pattern.pattern.converters
                                sub_pattern.pattern.converters.update(current_converters)
                                namespaces[namespace] = (p_pattern + prefix, sub_pattern)
                            for app_name, namespace_list in url_pattern.app_dict.items():
                                apps.setdefault(app_name, []).extend(namespace_list)
                        self._callback_strs.update(url_pattern._callback_strs)
                self._namespace_dict[language_code] = namespaces
                self._app_dict[language_code] = apps
                self._reverse_dict[language_code] = lookups
                self._populated = True
            finally:
                self._local.populating = False

        @property
        def reverse_dict(self):
            language_code = get_language()
            if language_code not in self._reverse_dict:
                self._populate()
            return self._reverse_dict[language_code]

        @property
        def namespace_dict(self):
            language_code = get_language()
            if language_code not in self._namespace_dict:
                self._populate()
            return self._namespace_dict[language_code]

        @property
        def app_dict(self):
            language_code = get_language()
            if language_code not in self._app_dict:
                self._populate()
            return self._app_dict[language_code]

        @staticmethod
        def _extend_tried(tried, pattern, sub_tried=None):
            if sub_tried is None:
                tried.append([pattern])
            else:
                tried.extend([pattern, *t] for t in sub_tried)

        @staticmethod
        def _join_route(route1, route2):
            """Join two routes, without the starting ^ in the second route."""
            if not route1:
                return route2
            route2 = route2.removeprefix("^")
            return route1 + route2

        def _is_callback(self, name):
            if not self._populated:
                self._populate()
            return name in self._callback_strs

        def resolve(self, path):
            path = str(path)  # path may be a reverse_lazy object
            tried = []
            match = self.pattern.match(path)
            if match:
                new_path, args, kwargs = match
                for pattern in self.url_patterns:
                    try:
                        sub_match = pattern.resolve(new_path)
                    except Resolver404 as e:
                        self._extend_tried(tried, pattern, e.args[0].get("tried"))
                    else:
                        if sub_match:
                            # Merge captured arguments in match with submatch
                            sub_match_dict = {**kwargs, **self.default_kwargs}
                            # Update the sub_match_dict with the kwargs from the sub_match.
                            sub_match_dict.update(sub_match.kwargs)
                            # If there are *any* named groups, ignore all non-named groups.
                            # Otherwise, pass all non-named arguments as positional
                            # arguments.
                            sub_match_args = sub_match.args
                            if not sub_match_dict:
                                sub_match_args = args + sub_match.args
                            current_route = (
                                ""
                                if isinstance(pattern, URLPattern)
                                else str(pattern.pattern)
                            )
                            self._extend_tried(tried, pattern, sub_match.tried)
                            return ResolverMatch(
                                sub_match.func,
                                sub_match_args,
                                sub_match_dict,
                                sub_match.url_name,
                                [self.app_name] + sub_match.app_names,
                                [self.namespace] + sub_match.namespaces,
                                self._join_route(current_route, sub_match.route),
                                tried,
                                captured_kwargs=sub_match.captured_kwargs,
                                extra_kwargs={
                                    **self.default_kwargs,
                                    **sub_match.extra_kwargs,
                                },
                            )
                        tried.append([pattern])
                raise Resolver404({"tried": tried, "path": new_path})
            raise Resolver404({"path": path})

        @cached_property
        def urlconf_module(self):
            if isinstance(self.urlconf_name, str):
                return import_module(self.urlconf_name)
            else:
                return self.urlconf_name

        @cached_property
        def url_patterns(self):
            # urlconf_module might be a valid set of patterns, so we default to it
            patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
            try:
                iter(patterns)
            except TypeError as e:
                msg = (
                    "The included URLconf '{name}' does not appear to have "
                    "any patterns in it. If you see the 'urlpatterns' variable "
                    "with valid patterns in the file then the issue is probably "
                    "caused by a circular import."
                )
                raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e
            return patterns

        def resolve_error_handler(self, view_type):
            callback = getattr(self.urlconf_module, "handler%s" % view_type, None)
            if not callback:
                # No handler specified in file; use lazy import, since
                # django.conf.urls imports this file.
                from django.conf import urls

                callback = getattr(urls, "handler%s" % view_type)
            return get_callable(callback)

        def reverse(self, lookup_view, *args, **kwargs):
            return self._reverse_with_prefix(lookup_view, "", *args, **kwargs)

        def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
            if args and kwargs:
                raise ValueError("Don't mix *args and **kwargs in call to reverse()!")

            if not self._populated:
                self._populate()

            possibilities = self.reverse_dict.getlist(lookup_view)

            for possibility, pattern, defaults, converters in possibilities:
                for result, params in possibility:
                    if args:
                        if len(args) != len(params):
                            continue
                        candidate_subs = dict(zip(params, args))
                    else:
                        if set(kwargs).symmetric_difference(params).difference(defaults):
                            continue
                        matches = True
                        for k, v in defaults.items():
                            if k in params:
                                continue
                            if kwargs.get(k, v) != v:
                                matches = False
                                break
                        if not matches:
                            continue
                        candidate_subs = kwargs
                    # Convert the candidate subs to text using Converter.to_url().
                    text_candidate_subs = {}
                    match = True
                    for k, v in candidate_subs.items():
                        if k in converters:
                            try:
                                text_candidate_subs[k] = converters[k].to_url(v)
                            except ValueError:
                                match = False
                                break
                        else:
                            text_candidate_subs[k] = str(v)
                    if not match:
                        continue
                    # WSGI provides decoded URLs, without %xx escapes, and the URL
                    # resolver operates on such URLs. First substitute arguments
                    # without quoting to build a decoded URL and look for a match.
                    # Then, if we have a match, redo the substitution with quoted
                    # arguments in order to return a properly encoded URL.
                    candidate_pat = _prefix.replace("%", "%%") + result
                    if re.search(
                        "^%s%s" % (re.escape(_prefix), pattern),
                        candidate_pat % text_candidate_subs,
                    ):
                        # safe characters from `pchar` definition of RFC 3986
                        url = quote(
                            candidate_pat % text_candidate_subs,
                            safe=RFC3986_SUBDELIMS + "/~:@",
                        )
                        # Don't allow construction of scheme relative urls.
                        return escape_leading_slashes(url)
            # lookup_view can be URL name or callable, but callables are not
            # friendly in error messages.
            m = getattr(lookup_view, "__module__", None)
            n = getattr(lookup_view, "__name__", None)
            if m is not None and n is not None:
                lookup_view_s = "%s.%s" % (m, n)
            else:
                lookup_view_s = lookup_view

            patterns = [pattern for (_, pattern, _, _) in possibilities]
            if patterns:
                if args:
                    arg_msg = "arguments '%s'" % (args,)
                elif kwargs:
                    arg_msg = "keyword arguments '%s'" % kwargs
                else:
                    arg_msg = "no arguments"
                msg = "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % (
                    lookup_view_s,
                    arg_msg,
                    len(patterns),
                    patterns,
                )
            else:
                msg = (
                    "Reverse for '%(view)s' not found. '%(view)s' is not "
                    "a valid view function or pattern name." % {"view": lookup_view_s}
                )
            raise NoReverseMatch(msg)