929 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			929 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import ast
 | 
						|
import re
 | 
						|
import typing as t
 | 
						|
from dataclasses import dataclass
 | 
						|
from string import Template
 | 
						|
from types import CodeType
 | 
						|
from urllib.parse import quote
 | 
						|
 | 
						|
from ..datastructures import iter_multi_items
 | 
						|
from ..urls import _urlencode
 | 
						|
from .converters import ValidationError
 | 
						|
 | 
						|
if t.TYPE_CHECKING:
 | 
						|
    from .converters import BaseConverter
 | 
						|
    from .map import Map
 | 
						|
 | 
						|
 | 
						|
class Weighting(t.NamedTuple):
 | 
						|
    number_static_weights: int
 | 
						|
    static_weights: list[tuple[int, int]]
 | 
						|
    number_argument_weights: int
 | 
						|
    argument_weights: list[int]
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class RulePart:
 | 
						|
    """A part of a rule.
 | 
						|
 | 
						|
    Rules can be represented by parts as delimited by `/` with
 | 
						|
    instances of this class representing those parts. The *content* is
 | 
						|
    either the raw content if *static* or a regex string to match
 | 
						|
    against. The *weight* can be used to order parts when matching.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    content: str
 | 
						|
    final: bool
 | 
						|
    static: bool
 | 
						|
    suffixed: bool
 | 
						|
    weight: Weighting
 | 
						|
 | 
						|
 | 
						|
_part_re = re.compile(
 | 
						|
    r"""
 | 
						|
    (?:
 | 
						|
        (?P<slash>/)                                 # a slash
 | 
						|
      |
 | 
						|
        (?P<static>[^</]+)                           # static rule data
 | 
						|
      |
 | 
						|
        (?:
 | 
						|
          <
 | 
						|
            (?:
 | 
						|
              (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name
 | 
						|
              (?:\((?P<arguments>.*?)\))?             # converter arguments
 | 
						|
              :                                       # variable delimiter
 | 
						|
            )?
 | 
						|
            (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)      # variable name
 | 
						|
           >
 | 
						|
        )
 | 
						|
    )
 | 
						|
    """,
 | 
						|
    re.VERBOSE,
 | 
						|
)
 | 
						|
 | 
						|
_simple_rule_re = re.compile(r"<([^>]+)>")
 | 
						|
_converter_args_re = re.compile(
 | 
						|
    r"""
 | 
						|
    \s*
 | 
						|
    ((?P<name>\w+)\s*=\s*)?
 | 
						|
    (?P<value>
 | 
						|
        True|False|
 | 
						|
        \d+.\d+|
 | 
						|
        \d+.|
 | 
						|
        \d+|
 | 
						|
        [\w\d_.]+|
 | 
						|
        [urUR]?(?P<stringval>"[^"]*?"|'[^']*')
 | 
						|
    )\s*,
 | 
						|
    """,
 | 
						|
    re.VERBOSE,
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False}
 | 
						|
 | 
						|
 | 
						|
def _find(value: str, target: str, pos: int) -> int:
 | 
						|
    """Find the *target* in *value* after *pos*.
 | 
						|
 | 
						|
    Returns the *value* length if *target* isn't found.
 | 
						|
    """
 | 
						|
    try:
 | 
						|
        return value.index(target, pos)
 | 
						|
    except ValueError:
 | 
						|
        return len(value)
 | 
						|
 | 
						|
 | 
						|
def _pythonize(value: str) -> None | bool | int | float | str:
 | 
						|
    if value in _PYTHON_CONSTANTS:
 | 
						|
        return _PYTHON_CONSTANTS[value]
 | 
						|
    for convert in int, float:
 | 
						|
        try:
 | 
						|
            return convert(value)
 | 
						|
        except ValueError:
 | 
						|
            pass
 | 
						|
    if value[:1] == value[-1:] and value[0] in "\"'":
 | 
						|
        value = value[1:-1]
 | 
						|
    return str(value)
 | 
						|
 | 
						|
 | 
						|
def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.Any]]:
 | 
						|
    argstr += ","
 | 
						|
    args = []
 | 
						|
    kwargs = {}
 | 
						|
    position = 0
 | 
						|
 | 
						|
    for item in _converter_args_re.finditer(argstr):
 | 
						|
        if item.start() != position:
 | 
						|
            raise ValueError(
 | 
						|
                f"Cannot parse converter argument '{argstr[position:item.start()]}'"
 | 
						|
            )
 | 
						|
 | 
						|
        value = item.group("stringval")
 | 
						|
        if value is None:
 | 
						|
            value = item.group("value")
 | 
						|
        value = _pythonize(value)
 | 
						|
        if not item.group("name"):
 | 
						|
            args.append(value)
 | 
						|
        else:
 | 
						|
            name = item.group("name")
 | 
						|
            kwargs[name] = value
 | 
						|
        position = item.end()
 | 
						|
 | 
						|
    return tuple(args), kwargs
 | 
						|
 | 
						|
 | 
						|
class RuleFactory:
 | 
						|
    """As soon as you have more complex URL setups it's a good idea to use rule
 | 
						|
    factories to avoid repetitive tasks.  Some of them are builtin, others can
 | 
						|
    be added by subclassing `RuleFactory` and overriding `get_rules`.
 | 
						|
    """
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterable[Rule]:
 | 
						|
        """Subclasses of `RuleFactory` have to override this method and return
 | 
						|
        an iterable of rules."""
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
 | 
						|
class Subdomain(RuleFactory):
 | 
						|
    """All URLs provided by this factory have the subdomain set to a
 | 
						|
    specific domain. For example if you want to use the subdomain for
 | 
						|
    the current language this can be a good setup::
 | 
						|
 | 
						|
        url_map = Map([
 | 
						|
            Rule('/', endpoint='#select_language'),
 | 
						|
            Subdomain('<string(length=2):lang_code>', [
 | 
						|
                Rule('/', endpoint='index'),
 | 
						|
                Rule('/about', endpoint='about'),
 | 
						|
                Rule('/help', endpoint='help')
 | 
						|
            ])
 | 
						|
        ])
 | 
						|
 | 
						|
    All the rules except for the ``'#select_language'`` endpoint will now
 | 
						|
    listen on a two letter long subdomain that holds the language code
 | 
						|
    for the current request.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None:
 | 
						|
        self.subdomain = subdomain
 | 
						|
        self.rules = rules
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterator[Rule]:
 | 
						|
        for rulefactory in self.rules:
 | 
						|
            for rule in rulefactory.get_rules(map):
 | 
						|
                rule = rule.empty()
 | 
						|
                rule.subdomain = self.subdomain
 | 
						|
                yield rule
 | 
						|
 | 
						|
 | 
						|
class Submount(RuleFactory):
 | 
						|
    """Like `Subdomain` but prefixes the URL rule with a given string::
 | 
						|
 | 
						|
        url_map = Map([
 | 
						|
            Rule('/', endpoint='index'),
 | 
						|
            Submount('/blog', [
 | 
						|
                Rule('/', endpoint='blog/index'),
 | 
						|
                Rule('/entry/<entry_slug>', endpoint='blog/show')
 | 
						|
            ])
 | 
						|
        ])
 | 
						|
 | 
						|
    Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None:
 | 
						|
        self.path = path.rstrip("/")
 | 
						|
        self.rules = rules
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterator[Rule]:
 | 
						|
        for rulefactory in self.rules:
 | 
						|
            for rule in rulefactory.get_rules(map):
 | 
						|
                rule = rule.empty()
 | 
						|
                rule.rule = self.path + rule.rule
 | 
						|
                yield rule
 | 
						|
 | 
						|
 | 
						|
class EndpointPrefix(RuleFactory):
 | 
						|
    """Prefixes all endpoints (which must be strings for this factory) with
 | 
						|
    another string. This can be useful for sub applications::
 | 
						|
 | 
						|
        url_map = Map([
 | 
						|
            Rule('/', endpoint='index'),
 | 
						|
            EndpointPrefix('blog/', [Submount('/blog', [
 | 
						|
                Rule('/', endpoint='index'),
 | 
						|
                Rule('/entry/<entry_slug>', endpoint='show')
 | 
						|
            ])])
 | 
						|
        ])
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None:
 | 
						|
        self.prefix = prefix
 | 
						|
        self.rules = rules
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterator[Rule]:
 | 
						|
        for rulefactory in self.rules:
 | 
						|
            for rule in rulefactory.get_rules(map):
 | 
						|
                rule = rule.empty()
 | 
						|
                rule.endpoint = self.prefix + rule.endpoint
 | 
						|
                yield rule
 | 
						|
 | 
						|
 | 
						|
class RuleTemplate:
 | 
						|
    """Returns copies of the rules wrapped and expands string templates in
 | 
						|
    the endpoint, rule, defaults or subdomain sections.
 | 
						|
 | 
						|
    Here a small example for such a rule template::
 | 
						|
 | 
						|
        from werkzeug.routing import Map, Rule, RuleTemplate
 | 
						|
 | 
						|
        resource = RuleTemplate([
 | 
						|
            Rule('/$name/', endpoint='$name.list'),
 | 
						|
            Rule('/$name/<int:id>', endpoint='$name.show')
 | 
						|
        ])
 | 
						|
 | 
						|
        url_map = Map([resource(name='user'), resource(name='page')])
 | 
						|
 | 
						|
    When a rule template is called the keyword arguments are used to
 | 
						|
    replace the placeholders in all the string parameters.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, rules: t.Iterable[Rule]) -> None:
 | 
						|
        self.rules = list(rules)
 | 
						|
 | 
						|
    def __call__(self, *args: t.Any, **kwargs: t.Any) -> RuleTemplateFactory:
 | 
						|
        return RuleTemplateFactory(self.rules, dict(*args, **kwargs))
 | 
						|
 | 
						|
 | 
						|
class RuleTemplateFactory(RuleFactory):
 | 
						|
    """A factory that fills in template variables into rules.  Used by
 | 
						|
    `RuleTemplate` internally.
 | 
						|
 | 
						|
    :internal:
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, rules: t.Iterable[RuleFactory], context: dict[str, t.Any]
 | 
						|
    ) -> None:
 | 
						|
        self.rules = rules
 | 
						|
        self.context = context
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterator[Rule]:
 | 
						|
        for rulefactory in self.rules:
 | 
						|
            for rule in rulefactory.get_rules(map):
 | 
						|
                new_defaults = subdomain = None
 | 
						|
                if rule.defaults:
 | 
						|
                    new_defaults = {}
 | 
						|
                    for key, value in rule.defaults.items():
 | 
						|
                        if isinstance(value, str):
 | 
						|
                            value = Template(value).substitute(self.context)
 | 
						|
                        new_defaults[key] = value
 | 
						|
                if rule.subdomain is not None:
 | 
						|
                    subdomain = Template(rule.subdomain).substitute(self.context)
 | 
						|
                new_endpoint = rule.endpoint
 | 
						|
                if isinstance(new_endpoint, str):
 | 
						|
                    new_endpoint = Template(new_endpoint).substitute(self.context)
 | 
						|
                yield Rule(
 | 
						|
                    Template(rule.rule).substitute(self.context),
 | 
						|
                    new_defaults,
 | 
						|
                    subdomain,
 | 
						|
                    rule.methods,
 | 
						|
                    rule.build_only,
 | 
						|
                    new_endpoint,
 | 
						|
                    rule.strict_slashes,
 | 
						|
                )
 | 
						|
 | 
						|
 | 
						|
_ASTT = t.TypeVar("_ASTT", bound=ast.AST)
 | 
						|
 | 
						|
 | 
						|
def _prefix_names(src: str, expected_type: type[_ASTT]) -> _ASTT:
 | 
						|
    """ast parse and prefix names with `.` to avoid collision with user vars"""
 | 
						|
    tree: ast.AST = ast.parse(src).body[0]
 | 
						|
    if isinstance(tree, ast.Expr):
 | 
						|
        tree = tree.value
 | 
						|
    if not isinstance(tree, expected_type):
 | 
						|
        raise TypeError(
 | 
						|
            f"AST node is of type {type(tree).__name__}, not {expected_type.__name__}"
 | 
						|
        )
 | 
						|
    for node in ast.walk(tree):
 | 
						|
        if isinstance(node, ast.Name):
 | 
						|
            node.id = f".{node.id}"
 | 
						|
    return tree
 | 
						|
 | 
						|
 | 
						|
_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()"
 | 
						|
_IF_KWARGS_URL_ENCODE_CODE = """\
 | 
						|
if kwargs:
 | 
						|
    params = self._encode_query_vars(kwargs)
 | 
						|
    q = "?" if params else ""
 | 
						|
else:
 | 
						|
    q = params = ""
 | 
						|
"""
 | 
						|
_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE, ast.If)
 | 
						|
_URL_ENCODE_AST_NAMES = (
 | 
						|
    _prefix_names("q", ast.Name),
 | 
						|
    _prefix_names("params", ast.Name),
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
class Rule(RuleFactory):
 | 
						|
    """A Rule represents one URL pattern.  There are some options for `Rule`
 | 
						|
    that change the way it behaves and are passed to the `Rule` constructor.
 | 
						|
    Note that besides the rule-string all arguments *must* be keyword arguments
 | 
						|
    in order to not break the application on Werkzeug upgrades.
 | 
						|
 | 
						|
    `string`
 | 
						|
        Rule strings basically are just normal URL paths with placeholders in
 | 
						|
        the format ``<converter(arguments):name>`` where the converter and the
 | 
						|
        arguments are optional.  If no converter is defined the `default`
 | 
						|
        converter is used which means `string` in the normal configuration.
 | 
						|
 | 
						|
        URL rules that end with a slash are branch URLs, others are leaves.
 | 
						|
        If you have `strict_slashes` enabled (which is the default), all
 | 
						|
        branch URLs that are matched without a trailing slash will trigger a
 | 
						|
        redirect to the same URL with the missing slash appended.
 | 
						|
 | 
						|
        The converters are defined on the `Map`.
 | 
						|
 | 
						|
    `endpoint`
 | 
						|
        The endpoint for this rule. This can be anything. A reference to a
 | 
						|
        function, a string, a number etc.  The preferred way is using a string
 | 
						|
        because the endpoint is used for URL generation.
 | 
						|
 | 
						|
    `defaults`
 | 
						|
        An optional dict with defaults for other rules with the same endpoint.
 | 
						|
        This is a bit tricky but useful if you want to have unique URLs::
 | 
						|
 | 
						|
            url_map = Map([
 | 
						|
                Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
 | 
						|
                Rule('/all/page/<int:page>', endpoint='all_entries')
 | 
						|
            ])
 | 
						|
 | 
						|
        If a user now visits ``http://example.com/all/page/1`` they will be
 | 
						|
        redirected to ``http://example.com/all/``.  If `redirect_defaults` is
 | 
						|
        disabled on the `Map` instance this will only affect the URL
 | 
						|
        generation.
 | 
						|
 | 
						|
    `subdomain`
 | 
						|
        The subdomain rule string for this rule. If not specified the rule
 | 
						|
        only matches for the `default_subdomain` of the map.  If the map is
 | 
						|
        not bound to a subdomain this feature is disabled.
 | 
						|
 | 
						|
        Can be useful if you want to have user profiles on different subdomains
 | 
						|
        and all subdomains are forwarded to your application::
 | 
						|
 | 
						|
            url_map = Map([
 | 
						|
                Rule('/', subdomain='<username>', endpoint='user/homepage'),
 | 
						|
                Rule('/stats', subdomain='<username>', endpoint='user/stats')
 | 
						|
            ])
 | 
						|
 | 
						|
    `methods`
 | 
						|
        A sequence of http methods this rule applies to.  If not specified, all
 | 
						|
        methods are allowed. For example this can be useful if you want different
 | 
						|
        endpoints for `POST` and `GET`.  If methods are defined and the path
 | 
						|
        matches but the method matched against is not in this list or in the
 | 
						|
        list of another rule for that path the error raised is of the type
 | 
						|
        `MethodNotAllowed` rather than `NotFound`.  If `GET` is present in the
 | 
						|
        list of methods and `HEAD` is not, `HEAD` is added automatically.
 | 
						|
 | 
						|
    `strict_slashes`
 | 
						|
        Override the `Map` setting for `strict_slashes` only for this rule. If
 | 
						|
        not specified the `Map` setting is used.
 | 
						|
 | 
						|
    `merge_slashes`
 | 
						|
        Override :attr:`Map.merge_slashes` for this rule.
 | 
						|
 | 
						|
    `build_only`
 | 
						|
        Set this to True and the rule will never match but will create a URL
 | 
						|
        that can be build. This is useful if you have resources on a subdomain
 | 
						|
        or folder that are not handled by the WSGI application (like static data)
 | 
						|
 | 
						|
    `redirect_to`
 | 
						|
        If given this must be either a string or callable.  In case of a
 | 
						|
        callable it's called with the url adapter that triggered the match and
 | 
						|
        the values of the URL as keyword arguments and has to return the target
 | 
						|
        for the redirect, otherwise it has to be a string with placeholders in
 | 
						|
        rule syntax::
 | 
						|
 | 
						|
            def foo_with_slug(adapter, id):
 | 
						|
                # ask the database for the slug for the old id.  this of
 | 
						|
                # course has nothing to do with werkzeug.
 | 
						|
                return f'foo/{Foo.get_slug_for_id(id)}'
 | 
						|
 | 
						|
            url_map = Map([
 | 
						|
                Rule('/foo/<slug>', endpoint='foo'),
 | 
						|
                Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'),
 | 
						|
                Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug)
 | 
						|
            ])
 | 
						|
 | 
						|
        When the rule is matched the routing system will raise a
 | 
						|
        `RequestRedirect` exception with the target for the redirect.
 | 
						|
 | 
						|
        Keep in mind that the URL will be joined against the URL root of the
 | 
						|
        script so don't use a leading slash on the target URL unless you
 | 
						|
        really mean root of that domain.
 | 
						|
 | 
						|
    `alias`
 | 
						|
        If enabled this rule serves as an alias for another rule with the same
 | 
						|
        endpoint and arguments.
 | 
						|
 | 
						|
    `host`
 | 
						|
        If provided and the URL map has host matching enabled this can be
 | 
						|
        used to provide a match rule for the whole host.  This also means
 | 
						|
        that the subdomain feature is disabled.
 | 
						|
 | 
						|
    `websocket`
 | 
						|
        If ``True``, this rule is only matches for WebSocket (``ws://``,
 | 
						|
        ``wss://``) requests. By default, rules will only match for HTTP
 | 
						|
        requests.
 | 
						|
 | 
						|
    .. versionchanged:: 2.1
 | 
						|
        Percent-encoded newlines (``%0a``), which are decoded by WSGI
 | 
						|
        servers, are considered when routing instead of terminating the
 | 
						|
        match early.
 | 
						|
 | 
						|
    .. versionadded:: 1.0
 | 
						|
        Added ``websocket``.
 | 
						|
 | 
						|
    .. versionadded:: 1.0
 | 
						|
        Added ``merge_slashes``.
 | 
						|
 | 
						|
    .. versionadded:: 0.7
 | 
						|
        Added ``alias`` and ``host``.
 | 
						|
 | 
						|
    .. versionchanged:: 0.6.1
 | 
						|
       ``HEAD`` is added to ``methods`` if ``GET`` is present.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        string: str,
 | 
						|
        defaults: t.Mapping[str, t.Any] | None = None,
 | 
						|
        subdomain: str | None = None,
 | 
						|
        methods: t.Iterable[str] | None = None,
 | 
						|
        build_only: bool = False,
 | 
						|
        endpoint: t.Any | None = None,
 | 
						|
        strict_slashes: bool | None = None,
 | 
						|
        merge_slashes: bool | None = None,
 | 
						|
        redirect_to: str | t.Callable[..., str] | None = None,
 | 
						|
        alias: bool = False,
 | 
						|
        host: str | None = None,
 | 
						|
        websocket: bool = False,
 | 
						|
    ) -> None:
 | 
						|
        if not string.startswith("/"):
 | 
						|
            raise ValueError(f"URL rule '{string}' must start with a slash.")
 | 
						|
 | 
						|
        self.rule = string
 | 
						|
        self.is_leaf = not string.endswith("/")
 | 
						|
        self.is_branch = string.endswith("/")
 | 
						|
 | 
						|
        self.map: Map = None  # type: ignore
 | 
						|
        self.strict_slashes = strict_slashes
 | 
						|
        self.merge_slashes = merge_slashes
 | 
						|
        self.subdomain = subdomain
 | 
						|
        self.host = host
 | 
						|
        self.defaults = defaults
 | 
						|
        self.build_only = build_only
 | 
						|
        self.alias = alias
 | 
						|
        self.websocket = websocket
 | 
						|
 | 
						|
        if methods is not None:
 | 
						|
            if isinstance(methods, str):
 | 
						|
                raise TypeError("'methods' should be a list of strings.")
 | 
						|
 | 
						|
            methods = {x.upper() for x in methods}
 | 
						|
 | 
						|
            if "HEAD" not in methods and "GET" in methods:
 | 
						|
                methods.add("HEAD")
 | 
						|
 | 
						|
            if websocket and methods - {"GET", "HEAD", "OPTIONS"}:
 | 
						|
                raise ValueError(
 | 
						|
                    "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods."
 | 
						|
                )
 | 
						|
 | 
						|
        self.methods = methods
 | 
						|
        self.endpoint: t.Any = endpoint
 | 
						|
        self.redirect_to = redirect_to
 | 
						|
 | 
						|
        if defaults:
 | 
						|
            self.arguments = set(map(str, defaults))
 | 
						|
        else:
 | 
						|
            self.arguments = set()
 | 
						|
 | 
						|
        self._converters: dict[str, BaseConverter] = {}
 | 
						|
        self._trace: list[tuple[bool, str]] = []
 | 
						|
        self._parts: list[RulePart] = []
 | 
						|
 | 
						|
    def empty(self) -> Rule:
 | 
						|
        """
 | 
						|
        Return an unbound copy of this rule.
 | 
						|
 | 
						|
        This can be useful if want to reuse an already bound URL for another
 | 
						|
        map.  See ``get_empty_kwargs`` to override what keyword arguments are
 | 
						|
        provided to the new copy.
 | 
						|
        """
 | 
						|
        return type(self)(self.rule, **self.get_empty_kwargs())
 | 
						|
 | 
						|
    def get_empty_kwargs(self) -> t.Mapping[str, t.Any]:
 | 
						|
        """
 | 
						|
        Provides kwargs for instantiating empty copy with empty()
 | 
						|
 | 
						|
        Use this method to provide custom keyword arguments to the subclass of
 | 
						|
        ``Rule`` when calling ``some_rule.empty()``.  Helpful when the subclass
 | 
						|
        has custom keyword arguments that are needed at instantiation.
 | 
						|
 | 
						|
        Must return a ``dict`` that will be provided as kwargs to the new
 | 
						|
        instance of ``Rule``, following the initial ``self.rule`` value which
 | 
						|
        is always provided as the first, required positional argument.
 | 
						|
        """
 | 
						|
        defaults = None
 | 
						|
        if self.defaults:
 | 
						|
            defaults = dict(self.defaults)
 | 
						|
        return dict(
 | 
						|
            defaults=defaults,
 | 
						|
            subdomain=self.subdomain,
 | 
						|
            methods=self.methods,
 | 
						|
            build_only=self.build_only,
 | 
						|
            endpoint=self.endpoint,
 | 
						|
            strict_slashes=self.strict_slashes,
 | 
						|
            redirect_to=self.redirect_to,
 | 
						|
            alias=self.alias,
 | 
						|
            host=self.host,
 | 
						|
        )
 | 
						|
 | 
						|
    def get_rules(self, map: Map) -> t.Iterator[Rule]:
 | 
						|
        yield self
 | 
						|
 | 
						|
    def refresh(self) -> None:
 | 
						|
        """Rebinds and refreshes the URL.  Call this if you modified the
 | 
						|
        rule in place.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        self.bind(self.map, rebind=True)
 | 
						|
 | 
						|
    def bind(self, map: Map, rebind: bool = False) -> None:
 | 
						|
        """Bind the url to a map and create a regular expression based on
 | 
						|
        the information from the rule itself and the defaults from the map.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        if self.map is not None and not rebind:
 | 
						|
            raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}")
 | 
						|
        self.map = map
 | 
						|
        if self.strict_slashes is None:
 | 
						|
            self.strict_slashes = map.strict_slashes
 | 
						|
        if self.merge_slashes is None:
 | 
						|
            self.merge_slashes = map.merge_slashes
 | 
						|
        if self.subdomain is None:
 | 
						|
            self.subdomain = map.default_subdomain
 | 
						|
        self.compile()
 | 
						|
 | 
						|
    def get_converter(
 | 
						|
        self,
 | 
						|
        variable_name: str,
 | 
						|
        converter_name: str,
 | 
						|
        args: tuple[t.Any, ...],
 | 
						|
        kwargs: t.Mapping[str, t.Any],
 | 
						|
    ) -> BaseConverter:
 | 
						|
        """Looks up the converter for the given parameter.
 | 
						|
 | 
						|
        .. versionadded:: 0.9
 | 
						|
        """
 | 
						|
        if converter_name not in self.map.converters:
 | 
						|
            raise LookupError(f"the converter {converter_name!r} does not exist")
 | 
						|
        return self.map.converters[converter_name](self.map, *args, **kwargs)
 | 
						|
 | 
						|
    def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str:
 | 
						|
        items: t.Iterable[tuple[str, str]] = iter_multi_items(query_vars)
 | 
						|
 | 
						|
        if self.map.sort_parameters:
 | 
						|
            items = sorted(items, key=self.map.sort_key)
 | 
						|
 | 
						|
        return _urlencode(items)
 | 
						|
 | 
						|
    def _parse_rule(self, rule: str) -> t.Iterable[RulePart]:
 | 
						|
        content = ""
 | 
						|
        static = True
 | 
						|
        argument_weights = []
 | 
						|
        static_weights: list[tuple[int, int]] = []
 | 
						|
        final = False
 | 
						|
        convertor_number = 0
 | 
						|
 | 
						|
        pos = 0
 | 
						|
        while pos < len(rule):
 | 
						|
            match = _part_re.match(rule, pos)
 | 
						|
            if match is None:
 | 
						|
                raise ValueError(f"malformed url rule: {rule!r}")
 | 
						|
 | 
						|
            data = match.groupdict()
 | 
						|
            if data["static"] is not None:
 | 
						|
                static_weights.append((len(static_weights), -len(data["static"])))
 | 
						|
                self._trace.append((False, data["static"]))
 | 
						|
                content += data["static"] if static else re.escape(data["static"])
 | 
						|
 | 
						|
            if data["variable"] is not None:
 | 
						|
                if static:
 | 
						|
                    # Switching content to represent regex, hence the need to escape
 | 
						|
                    content = re.escape(content)
 | 
						|
                static = False
 | 
						|
                c_args, c_kwargs = parse_converter_args(data["arguments"] or "")
 | 
						|
                convobj = self.get_converter(
 | 
						|
                    data["variable"], data["converter"] or "default", c_args, c_kwargs
 | 
						|
                )
 | 
						|
                self._converters[data["variable"]] = convobj
 | 
						|
                self.arguments.add(data["variable"])
 | 
						|
                if not convobj.part_isolating:
 | 
						|
                    final = True
 | 
						|
                content += f"(?P<__werkzeug_{convertor_number}>{convobj.regex})"
 | 
						|
                convertor_number += 1
 | 
						|
                argument_weights.append(convobj.weight)
 | 
						|
                self._trace.append((True, data["variable"]))
 | 
						|
 | 
						|
            if data["slash"] is not None:
 | 
						|
                self._trace.append((False, "/"))
 | 
						|
                if final:
 | 
						|
                    content += "/"
 | 
						|
                else:
 | 
						|
                    if not static:
 | 
						|
                        content += r"\Z"
 | 
						|
                    weight = Weighting(
 | 
						|
                        -len(static_weights),
 | 
						|
                        static_weights,
 | 
						|
                        -len(argument_weights),
 | 
						|
                        argument_weights,
 | 
						|
                    )
 | 
						|
                    yield RulePart(
 | 
						|
                        content=content,
 | 
						|
                        final=final,
 | 
						|
                        static=static,
 | 
						|
                        suffixed=False,
 | 
						|
                        weight=weight,
 | 
						|
                    )
 | 
						|
                    content = ""
 | 
						|
                    static = True
 | 
						|
                    argument_weights = []
 | 
						|
                    static_weights = []
 | 
						|
                    final = False
 | 
						|
                    convertor_number = 0
 | 
						|
 | 
						|
            pos = match.end()
 | 
						|
 | 
						|
        suffixed = False
 | 
						|
        if final and content[-1] == "/":
 | 
						|
            # If a converter is part_isolating=False (matches slashes) and ends with a
 | 
						|
            # slash, augment the regex to support slash redirects.
 | 
						|
            suffixed = True
 | 
						|
            content = content[:-1] + "(?<!/)(/?)"
 | 
						|
        if not static:
 | 
						|
            content += r"\Z"
 | 
						|
        weight = Weighting(
 | 
						|
            -len(static_weights),
 | 
						|
            static_weights,
 | 
						|
            -len(argument_weights),
 | 
						|
            argument_weights,
 | 
						|
        )
 | 
						|
        yield RulePart(
 | 
						|
            content=content,
 | 
						|
            final=final,
 | 
						|
            static=static,
 | 
						|
            suffixed=suffixed,
 | 
						|
            weight=weight,
 | 
						|
        )
 | 
						|
        if suffixed:
 | 
						|
            yield RulePart(
 | 
						|
                content="", final=False, static=True, suffixed=False, weight=weight
 | 
						|
            )
 | 
						|
 | 
						|
    def compile(self) -> None:
 | 
						|
        """Compiles the regular expression and stores it."""
 | 
						|
        assert self.map is not None, "rule not bound"
 | 
						|
 | 
						|
        if self.map.host_matching:
 | 
						|
            domain_rule = self.host or ""
 | 
						|
        else:
 | 
						|
            domain_rule = self.subdomain or ""
 | 
						|
        self._parts = []
 | 
						|
        self._trace = []
 | 
						|
        self._converters = {}
 | 
						|
        if domain_rule == "":
 | 
						|
            self._parts = [
 | 
						|
                RulePart(
 | 
						|
                    content="",
 | 
						|
                    final=False,
 | 
						|
                    static=True,
 | 
						|
                    suffixed=False,
 | 
						|
                    weight=Weighting(0, [], 0, []),
 | 
						|
                )
 | 
						|
            ]
 | 
						|
        else:
 | 
						|
            self._parts.extend(self._parse_rule(domain_rule))
 | 
						|
        self._trace.append((False, "|"))
 | 
						|
        rule = self.rule
 | 
						|
        if self.merge_slashes:
 | 
						|
            rule = re.sub("/{2,}?", "/", self.rule)
 | 
						|
        self._parts.extend(self._parse_rule(rule))
 | 
						|
 | 
						|
        self._build: t.Callable[..., tuple[str, str]]
 | 
						|
        self._build = self._compile_builder(False).__get__(self, None)
 | 
						|
        self._build_unknown: t.Callable[..., tuple[str, str]]
 | 
						|
        self._build_unknown = self._compile_builder(True).__get__(self, None)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _get_func_code(code: CodeType, name: str) -> t.Callable[..., tuple[str, str]]:
 | 
						|
        globs: dict[str, t.Any] = {}
 | 
						|
        locs: dict[str, t.Any] = {}
 | 
						|
        exec(code, globs, locs)
 | 
						|
        return locs[name]  # type: ignore
 | 
						|
 | 
						|
    def _compile_builder(
 | 
						|
        self, append_unknown: bool = True
 | 
						|
    ) -> t.Callable[..., tuple[str, str]]:
 | 
						|
        defaults = self.defaults or {}
 | 
						|
        dom_ops: list[tuple[bool, str]] = []
 | 
						|
        url_ops: list[tuple[bool, str]] = []
 | 
						|
 | 
						|
        opl = dom_ops
 | 
						|
        for is_dynamic, data in self._trace:
 | 
						|
            if data == "|" and opl is dom_ops:
 | 
						|
                opl = url_ops
 | 
						|
                continue
 | 
						|
            # this seems like a silly case to ever come up but:
 | 
						|
            # if a default is given for a value that appears in the rule,
 | 
						|
            # resolve it to a constant ahead of time
 | 
						|
            if is_dynamic and data in defaults:
 | 
						|
                data = self._converters[data].to_url(defaults[data])
 | 
						|
                opl.append((False, data))
 | 
						|
            elif not is_dynamic:
 | 
						|
                # safe = https://url.spec.whatwg.org/#url-path-segment-string
 | 
						|
                opl.append((False, quote(data, safe="!$&'()*+,/:;=@")))
 | 
						|
            else:
 | 
						|
                opl.append((True, data))
 | 
						|
 | 
						|
        def _convert(elem: str) -> ast.Call:
 | 
						|
            ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem), ast.Call)
 | 
						|
            ret.args = [ast.Name(elem, ast.Load())]
 | 
						|
            return ret
 | 
						|
 | 
						|
        def _parts(ops: list[tuple[bool, str]]) -> list[ast.expr]:
 | 
						|
            parts: list[ast.expr] = [
 | 
						|
                _convert(elem) if is_dynamic else ast.Constant(elem)
 | 
						|
                for is_dynamic, elem in ops
 | 
						|
            ]
 | 
						|
            parts = parts or [ast.Constant("")]
 | 
						|
            # constant fold
 | 
						|
            ret = [parts[0]]
 | 
						|
            for p in parts[1:]:
 | 
						|
                if isinstance(p, ast.Constant) and isinstance(ret[-1], ast.Constant):
 | 
						|
                    ret[-1] = ast.Constant(ret[-1].value + p.value)
 | 
						|
                else:
 | 
						|
                    ret.append(p)
 | 
						|
            return ret
 | 
						|
 | 
						|
        dom_parts = _parts(dom_ops)
 | 
						|
        url_parts = _parts(url_ops)
 | 
						|
        body: list[ast.stmt]
 | 
						|
        if not append_unknown:
 | 
						|
            body = []
 | 
						|
        else:
 | 
						|
            body = [_IF_KWARGS_URL_ENCODE_AST]
 | 
						|
            url_parts.extend(_URL_ENCODE_AST_NAMES)
 | 
						|
 | 
						|
        def _join(parts: list[ast.expr]) -> ast.expr:
 | 
						|
            if len(parts) == 1:  # shortcut
 | 
						|
                return parts[0]
 | 
						|
            return ast.JoinedStr(parts)
 | 
						|
 | 
						|
        body.append(
 | 
						|
            ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load()))
 | 
						|
        )
 | 
						|
 | 
						|
        pargs = [
 | 
						|
            elem
 | 
						|
            for is_dynamic, elem in dom_ops + url_ops
 | 
						|
            if is_dynamic and elem not in defaults
 | 
						|
        ]
 | 
						|
        kargs = [str(k) for k in defaults]
 | 
						|
 | 
						|
        func_ast = _prefix_names("def _(): pass", ast.FunctionDef)
 | 
						|
        func_ast.name = f"<builder:{self.rule!r}>"
 | 
						|
        func_ast.args.args.append(ast.arg(".self", None))
 | 
						|
        for arg in pargs + kargs:
 | 
						|
            func_ast.args.args.append(ast.arg(arg, None))
 | 
						|
        func_ast.args.kwarg = ast.arg(".kwargs", None)
 | 
						|
        for _ in kargs:
 | 
						|
            func_ast.args.defaults.append(ast.Constant(""))
 | 
						|
        func_ast.body = body
 | 
						|
 | 
						|
        # Use `ast.parse` instead of `ast.Module` for better portability, since the
 | 
						|
        # signature of `ast.Module` can change.
 | 
						|
        module = ast.parse("")
 | 
						|
        module.body = [func_ast]
 | 
						|
 | 
						|
        # mark everything as on line 1, offset 0
 | 
						|
        # less error-prone than `ast.fix_missing_locations`
 | 
						|
        # bad line numbers cause an assert to fail in debug builds
 | 
						|
        for node in ast.walk(module):
 | 
						|
            if "lineno" in node._attributes:
 | 
						|
                node.lineno = 1  # type: ignore[attr-defined]
 | 
						|
            if "end_lineno" in node._attributes:
 | 
						|
                node.end_lineno = node.lineno  # type: ignore[attr-defined]
 | 
						|
            if "col_offset" in node._attributes:
 | 
						|
                node.col_offset = 0  # type: ignore[attr-defined]
 | 
						|
            if "end_col_offset" in node._attributes:
 | 
						|
                node.end_col_offset = node.col_offset  # type: ignore[attr-defined]
 | 
						|
 | 
						|
        code = compile(module, "<werkzeug routing>", "exec")
 | 
						|
        return self._get_func_code(code, func_ast.name)
 | 
						|
 | 
						|
    def build(
 | 
						|
        self, values: t.Mapping[str, t.Any], append_unknown: bool = True
 | 
						|
    ) -> tuple[str, str] | None:
 | 
						|
        """Assembles the relative url for that rule and the subdomain.
 | 
						|
        If building doesn't work for some reasons `None` is returned.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            if append_unknown:
 | 
						|
                return self._build_unknown(**values)
 | 
						|
            else:
 | 
						|
                return self._build(**values)
 | 
						|
        except ValidationError:
 | 
						|
            return None
 | 
						|
 | 
						|
    def provides_defaults_for(self, rule: Rule) -> bool:
 | 
						|
        """Check if this rule has defaults for a given rule.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        return bool(
 | 
						|
            not self.build_only
 | 
						|
            and self.defaults
 | 
						|
            and self.endpoint == rule.endpoint
 | 
						|
            and self != rule
 | 
						|
            and self.arguments == rule.arguments
 | 
						|
        )
 | 
						|
 | 
						|
    def suitable_for(
 | 
						|
        self, values: t.Mapping[str, t.Any], method: str | None = None
 | 
						|
    ) -> bool:
 | 
						|
        """Check if the dict of values has enough data for url generation.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        # if a method was given explicitly and that method is not supported
 | 
						|
        # by this rule, this rule is not suitable.
 | 
						|
        if (
 | 
						|
            method is not None
 | 
						|
            and self.methods is not None
 | 
						|
            and method not in self.methods
 | 
						|
        ):
 | 
						|
            return False
 | 
						|
 | 
						|
        defaults = self.defaults or ()
 | 
						|
 | 
						|
        # all arguments required must be either in the defaults dict or
 | 
						|
        # the value dictionary otherwise it's not suitable
 | 
						|
        for key in self.arguments:
 | 
						|
            if key not in defaults and key not in values:
 | 
						|
                return False
 | 
						|
 | 
						|
        # in case defaults are given we ensure that either the value was
 | 
						|
        # skipped or the value is the same as the default value.
 | 
						|
        if defaults:
 | 
						|
            for key, value in defaults.items():
 | 
						|
                if key in values and value != values[key]:
 | 
						|
                    return False
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def build_compare_key(self) -> tuple[int, int, int]:
 | 
						|
        """The build compare key for sorting.
 | 
						|
 | 
						|
        :internal:
 | 
						|
        """
 | 
						|
        return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ()))
 | 
						|
 | 
						|
    def __eq__(self, other: object) -> bool:
 | 
						|
        return isinstance(other, type(self)) and self._trace == other._trace
 | 
						|
 | 
						|
    __hash__ = None  # type: ignore
 | 
						|
 | 
						|
    def __str__(self) -> str:
 | 
						|
        return self.rule
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        if self.map is None:
 | 
						|
            return f"<{type(self).__name__} (unbound)>"
 | 
						|
        parts = []
 | 
						|
        for is_dynamic, data in self._trace:
 | 
						|
            if is_dynamic:
 | 
						|
                parts.append(f"<{data}>")
 | 
						|
            else:
 | 
						|
                parts.append(data)
 | 
						|
        parts_str = "".join(parts).lstrip("|")
 | 
						|
        methods = f" ({', '.join(self.methods)})" if self.methods is not None else ""
 | 
						|
        return f"<{type(self).__name__} {parts_str!r}{methods} -> {self.endpoint}>"
 |