1from __future__ import annotations 
    2 
    3import ast 
    4import re 
    5import typing as t 
    6from dataclasses import dataclass 
    7from string import Template 
    8from types import CodeType 
    9from urllib.parse import quote 
    10 
    11from ..datastructures import iter_multi_items 
    12from ..urls import _urlencode 
    13from .converters import ValidationError 
    14 
    15if t.TYPE_CHECKING: 
    16    from .converters import BaseConverter 
    17    from .map import Map 
    18 
    19 
    20class Weighting(t.NamedTuple): 
    21    number_static_weights: int 
    22    static_weights: list[tuple[int, int]] 
    23    number_argument_weights: int 
    24    argument_weights: list[int] 
    25 
    26 
    27@dataclass 
    28class RulePart: 
    29    """A part of a rule. 
    30 
    31    Rules can be represented by parts as delimited by `/` with 
    32    instances of this class representing those parts. The *content* is 
    33    either the raw content if *static* or a regex string to match 
    34    against. The *weight* can be used to order parts when matching. 
    35 
    36    """ 
    37 
    38    content: str 
    39    final: bool 
    40    static: bool 
    41    suffixed: bool 
    42    weight: Weighting 
    43 
    44 
    45_part_re = re.compile( 
    46    r""" 
    47    (?: 
    48        (?P<slash>/)                                 # a slash 
    49      | 
    50        (?P<static>[^</]+)                           # static rule data 
    51      | 
    52        (?: 
    53          < 
    54            (?: 
    55              (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)   # converter name 
    56              (?:\((?P<arguments>.*?)\))?             # converter arguments 
    57              :                                       # variable delimiter 
    58            )? 
    59            (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)      # variable name 
    60           > 
    61        ) 
    62    ) 
    63    """, 
    64    re.VERBOSE, 
    65) 
    66 
    67_simple_rule_re = re.compile(r"<([^>]+)>") 
    68_converter_args_re = re.compile( 
    69    r""" 
    70    \s* 
    71    ((?P<name>\w+)\s*=\s*)? 
    72    (?P<value> 
    73        True|False| 
    74        \d+.\d+| 
    75        \d+.| 
    76        \d+| 
    77        [\w\d_.]+| 
    78        [urUR]?(?P<stringval>"[^"]*?"|'[^']*') 
    79    )\s*, 
    80    """, 
    81    re.VERBOSE, 
    82) 
    83 
    84 
    85_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} 
    86 
    87 
    88def _find(value: str, target: str, pos: int) -> int: 
    89    """Find the *target* in *value* after *pos*. 
    90 
    91    Returns the *value* length if *target* isn't found. 
    92    """ 
    93    try: 
    94        return value.index(target, pos) 
    95    except ValueError: 
    96        return len(value) 
    97 
    98 
    99def _pythonize(value: str) -> None | bool | int | float | str: 
    100    if value in _PYTHON_CONSTANTS: 
    101        return _PYTHON_CONSTANTS[value] 
    102    for convert in int, float: 
    103        try: 
    104            return convert(value)  # type: ignore 
    105        except ValueError: 
    106            pass 
    107    if value[:1] == value[-1:] and value[0] in "\"'": 
    108        value = value[1:-1] 
    109    return str(value) 
    110 
    111 
    112def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.Any]]: 
    113    argstr += "," 
    114    args = [] 
    115    kwargs = {} 
    116    position = 0 
    117 
    118    for item in _converter_args_re.finditer(argstr): 
    119        if item.start() != position: 
    120            raise ValueError( 
    121                f"Cannot parse converter argument '{argstr[position:item.start()]}'" 
    122            ) 
    123 
    124        value = item.group("stringval") 
    125        if value is None: 
    126            value = item.group("value") 
    127        value = _pythonize(value) 
    128        if not item.group("name"): 
    129            args.append(value) 
    130        else: 
    131            name = item.group("name") 
    132            kwargs[name] = value 
    133        position = item.end() 
    134 
    135    return tuple(args), kwargs 
    136 
    137 
    138class RuleFactory: 
    139    """As soon as you have more complex URL setups it's a good idea to use rule 
    140    factories to avoid repetitive tasks.  Some of them are builtin, others can 
    141    be added by subclassing `RuleFactory` and overriding `get_rules`. 
    142    """ 
    143 
    144    def get_rules(self, map: Map) -> t.Iterable[Rule]: 
    145        """Subclasses of `RuleFactory` have to override this method and return 
    146        an iterable of rules.""" 
    147        raise NotImplementedError() 
    148 
    149 
    150class Subdomain(RuleFactory): 
    151    """All URLs provided by this factory have the subdomain set to a 
    152    specific domain. For example if you want to use the subdomain for 
    153    the current language this can be a good setup:: 
    154 
    155        url_map = Map([ 
    156            Rule('/', endpoint='#select_language'), 
    157            Subdomain('<string(length=2):lang_code>', [ 
    158                Rule('/', endpoint='index'), 
    159                Rule('/about', endpoint='about'), 
    160                Rule('/help', endpoint='help') 
    161            ]) 
    162        ]) 
    163 
    164    All the rules except for the ``'#select_language'`` endpoint will now 
    165    listen on a two letter long subdomain that holds the language code 
    166    for the current request. 
    167    """ 
    168 
    169    def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: 
    170        self.subdomain = subdomain 
    171        self.rules = rules 
    172 
    173    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    174        for rulefactory in self.rules: 
    175            for rule in rulefactory.get_rules(map): 
    176                rule = rule.empty() 
    177                rule.subdomain = self.subdomain 
    178                yield rule 
    179 
    180 
    181class Submount(RuleFactory): 
    182    """Like `Subdomain` but prefixes the URL rule with a given string:: 
    183 
    184        url_map = Map([ 
    185            Rule('/', endpoint='index'), 
    186            Submount('/blog', [ 
    187                Rule('/', endpoint='blog/index'), 
    188                Rule('/entry/<entry_slug>', endpoint='blog/show') 
    189            ]) 
    190        ]) 
    191 
    192    Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``. 
    193    """ 
    194 
    195    def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: 
    196        self.path = path.rstrip("/") 
    197        self.rules = rules 
    198 
    199    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    200        for rulefactory in self.rules: 
    201            for rule in rulefactory.get_rules(map): 
    202                rule = rule.empty() 
    203                rule.rule = self.path + rule.rule 
    204                yield rule 
    205 
    206 
    207class EndpointPrefix(RuleFactory): 
    208    """Prefixes all endpoints (which must be strings for this factory) with 
    209    another string. This can be useful for sub applications:: 
    210 
    211        url_map = Map([ 
    212            Rule('/', endpoint='index'), 
    213            EndpointPrefix('blog/', [Submount('/blog', [ 
    214                Rule('/', endpoint='index'), 
    215                Rule('/entry/<entry_slug>', endpoint='show') 
    216            ])]) 
    217        ]) 
    218    """ 
    219 
    220    def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: 
    221        self.prefix = prefix 
    222        self.rules = rules 
    223 
    224    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    225        for rulefactory in self.rules: 
    226            for rule in rulefactory.get_rules(map): 
    227                rule = rule.empty() 
    228                rule.endpoint = self.prefix + rule.endpoint 
    229                yield rule 
    230 
    231 
    232class RuleTemplate: 
    233    """Returns copies of the rules wrapped and expands string templates in 
    234    the endpoint, rule, defaults or subdomain sections. 
    235 
    236    Here a small example for such a rule template:: 
    237 
    238        from werkzeug.routing import Map, Rule, RuleTemplate 
    239 
    240        resource = RuleTemplate([ 
    241            Rule('/$name/', endpoint='$name.list'), 
    242            Rule('/$name/<int:id>', endpoint='$name.show') 
    243        ]) 
    244 
    245        url_map = Map([resource(name='user'), resource(name='page')]) 
    246 
    247    When a rule template is called the keyword arguments are used to 
    248    replace the placeholders in all the string parameters. 
    249    """ 
    250 
    251    def __init__(self, rules: t.Iterable[Rule]) -> None: 
    252        self.rules = list(rules) 
    253 
    254    def __call__(self, *args: t.Any, **kwargs: t.Any) -> RuleTemplateFactory: 
    255        return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) 
    256 
    257 
    258class RuleTemplateFactory(RuleFactory): 
    259    """A factory that fills in template variables into rules.  Used by 
    260    `RuleTemplate` internally. 
    261 
    262    :internal: 
    263    """ 
    264 
    265    def __init__( 
    266        self, rules: t.Iterable[RuleFactory], context: dict[str, t.Any] 
    267    ) -> None: 
    268        self.rules = rules 
    269        self.context = context 
    270 
    271    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    272        for rulefactory in self.rules: 
    273            for rule in rulefactory.get_rules(map): 
    274                new_defaults = subdomain = None 
    275                if rule.defaults: 
    276                    new_defaults = {} 
    277                    for key, value in rule.defaults.items(): 
    278                        if isinstance(value, str): 
    279                            value = Template(value).substitute(self.context) 
    280                        new_defaults[key] = value 
    281                if rule.subdomain is not None: 
    282                    subdomain = Template(rule.subdomain).substitute(self.context) 
    283                new_endpoint = rule.endpoint 
    284                if isinstance(new_endpoint, str): 
    285                    new_endpoint = Template(new_endpoint).substitute(self.context) 
    286                yield Rule( 
    287                    Template(rule.rule).substitute(self.context), 
    288                    new_defaults, 
    289                    subdomain, 
    290                    rule.methods, 
    291                    rule.build_only, 
    292                    new_endpoint, 
    293                    rule.strict_slashes, 
    294                ) 
    295 
    296 
    297def _prefix_names(src: str) -> ast.stmt: 
    298    """ast parse and prefix names with `.` to avoid collision with user vars""" 
    299    tree = ast.parse(src).body[0] 
    300    if isinstance(tree, ast.Expr): 
    301        tree = tree.value  # type: ignore 
    302    for node in ast.walk(tree): 
    303        if isinstance(node, ast.Name): 
    304            node.id = f".{node.id}" 
    305    return tree 
    306 
    307 
    308_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" 
    309_IF_KWARGS_URL_ENCODE_CODE = """\ 
    310if kwargs: 
    311    params = self._encode_query_vars(kwargs) 
    312    q = "?" if params else "" 
    313else: 
    314    q = params = "" 
    315""" 
    316_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) 
    317_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) 
    318 
    319 
    320class Rule(RuleFactory): 
    321    """A Rule represents one URL pattern.  There are some options for `Rule` 
    322    that change the way it behaves and are passed to the `Rule` constructor. 
    323    Note that besides the rule-string all arguments *must* be keyword arguments 
    324    in order to not break the application on Werkzeug upgrades. 
    325 
    326    `string` 
    327        Rule strings basically are just normal URL paths with placeholders in 
    328        the format ``<converter(arguments):name>`` where the converter and the 
    329        arguments are optional.  If no converter is defined the `default` 
    330        converter is used which means `string` in the normal configuration. 
    331 
    332        URL rules that end with a slash are branch URLs, others are leaves. 
    333        If you have `strict_slashes` enabled (which is the default), all 
    334        branch URLs that are matched without a trailing slash will trigger a 
    335        redirect to the same URL with the missing slash appended. 
    336 
    337        The converters are defined on the `Map`. 
    338 
    339    `endpoint` 
    340        The endpoint for this rule. This can be anything. A reference to a 
    341        function, a string, a number etc.  The preferred way is using a string 
    342        because the endpoint is used for URL generation. 
    343 
    344    `defaults` 
    345        An optional dict with defaults for other rules with the same endpoint. 
    346        This is a bit tricky but useful if you want to have unique URLs:: 
    347 
    348            url_map = Map([ 
    349                Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), 
    350                Rule('/all/page/<int:page>', endpoint='all_entries') 
    351            ]) 
    352 
    353        If a user now visits ``http://example.com/all/page/1`` they will be 
    354        redirected to ``http://example.com/all/``.  If `redirect_defaults` is 
    355        disabled on the `Map` instance this will only affect the URL 
    356        generation. 
    357 
    358    `subdomain` 
    359        The subdomain rule string for this rule. If not specified the rule 
    360        only matches for the `default_subdomain` of the map.  If the map is 
    361        not bound to a subdomain this feature is disabled. 
    362 
    363        Can be useful if you want to have user profiles on different subdomains 
    364        and all subdomains are forwarded to your application:: 
    365 
    366            url_map = Map([ 
    367                Rule('/', subdomain='<username>', endpoint='user/homepage'), 
    368                Rule('/stats', subdomain='<username>', endpoint='user/stats') 
    369            ]) 
    370 
    371    `methods` 
    372        A sequence of http methods this rule applies to.  If not specified, all 
    373        methods are allowed. For example this can be useful if you want different 
    374        endpoints for `POST` and `GET`.  If methods are defined and the path 
    375        matches but the method matched against is not in this list or in the 
    376        list of another rule for that path the error raised is of the type 
    377        `MethodNotAllowed` rather than `NotFound`.  If `GET` is present in the 
    378        list of methods and `HEAD` is not, `HEAD` is added automatically. 
    379 
    380    `strict_slashes` 
    381        Override the `Map` setting for `strict_slashes` only for this rule. If 
    382        not specified the `Map` setting is used. 
    383 
    384    `merge_slashes` 
    385        Override :attr:`Map.merge_slashes` for this rule. 
    386 
    387    `build_only` 
    388        Set this to True and the rule will never match but will create a URL 
    389        that can be build. This is useful if you have resources on a subdomain 
    390        or folder that are not handled by the WSGI application (like static data) 
    391 
    392    `redirect_to` 
    393        If given this must be either a string or callable.  In case of a 
    394        callable it's called with the url adapter that triggered the match and 
    395        the values of the URL as keyword arguments and has to return the target 
    396        for the redirect, otherwise it has to be a string with placeholders in 
    397        rule syntax:: 
    398 
    399            def foo_with_slug(adapter, id): 
    400                # ask the database for the slug for the old id.  this of 
    401                # course has nothing to do with werkzeug. 
    402                return f'foo/{Foo.get_slug_for_id(id)}' 
    403 
    404            url_map = Map([ 
    405                Rule('/foo/<slug>', endpoint='foo'), 
    406                Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'), 
    407                Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug) 
    408            ]) 
    409 
    410        When the rule is matched the routing system will raise a 
    411        `RequestRedirect` exception with the target for the redirect. 
    412 
    413        Keep in mind that the URL will be joined against the URL root of the 
    414        script so don't use a leading slash on the target URL unless you 
    415        really mean root of that domain. 
    416 
    417    `alias` 
    418        If enabled this rule serves as an alias for another rule with the same 
    419        endpoint and arguments. 
    420 
    421    `host` 
    422        If provided and the URL map has host matching enabled this can be 
    423        used to provide a match rule for the whole host.  This also means 
    424        that the subdomain feature is disabled. 
    425 
    426    `websocket` 
    427        If ``True``, this rule is only matches for WebSocket (``ws://``, 
    428        ``wss://``) requests. By default, rules will only match for HTTP 
    429        requests. 
    430 
    431    .. versionchanged:: 2.1 
    432        Percent-encoded newlines (``%0a``), which are decoded by WSGI 
    433        servers, are considered when routing instead of terminating the 
    434        match early. 
    435 
    436    .. versionadded:: 1.0 
    437        Added ``websocket``. 
    438 
    439    .. versionadded:: 1.0 
    440        Added ``merge_slashes``. 
    441 
    442    .. versionadded:: 0.7 
    443        Added ``alias`` and ``host``. 
    444 
    445    .. versionchanged:: 0.6.1 
    446       ``HEAD`` is added to ``methods`` if ``GET`` is present. 
    447    """ 
    448 
    449    def __init__( 
    450        self, 
    451        string: str, 
    452        defaults: t.Mapping[str, t.Any] | None = None, 
    453        subdomain: str | None = None, 
    454        methods: t.Iterable[str] | None = None, 
    455        build_only: bool = False, 
    456        endpoint: t.Any | None = None, 
    457        strict_slashes: bool | None = None, 
    458        merge_slashes: bool | None = None, 
    459        redirect_to: str | t.Callable[..., str] | None = None, 
    460        alias: bool = False, 
    461        host: str | None = None, 
    462        websocket: bool = False, 
    463    ) -> None: 
    464        if not string.startswith("/"): 
    465            raise ValueError(f"URL rule '{string}' must start with a slash.") 
    466 
    467        self.rule = string 
    468        self.is_leaf = not string.endswith("/") 
    469        self.is_branch = string.endswith("/") 
    470 
    471        self.map: Map = None  # type: ignore 
    472        self.strict_slashes = strict_slashes 
    473        self.merge_slashes = merge_slashes 
    474        self.subdomain = subdomain 
    475        self.host = host 
    476        self.defaults = defaults 
    477        self.build_only = build_only 
    478        self.alias = alias 
    479        self.websocket = websocket 
    480 
    481        if methods is not None: 
    482            if isinstance(methods, str): 
    483                raise TypeError("'methods' should be a list of strings.") 
    484 
    485            methods = {x.upper() for x in methods} 
    486 
    487            if "HEAD" not in methods and "GET" in methods: 
    488                methods.add("HEAD") 
    489 
    490            if websocket and methods - {"GET", "HEAD", "OPTIONS"}: 
    491                raise ValueError( 
    492                    "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." 
    493                ) 
    494 
    495        self.methods = methods 
    496        self.endpoint: t.Any = endpoint 
    497        self.redirect_to = redirect_to 
    498 
    499        if defaults: 
    500            self.arguments = set(map(str, defaults)) 
    501        else: 
    502            self.arguments = set() 
    503 
    504        self._converters: dict[str, BaseConverter] = {} 
    505        self._trace: list[tuple[bool, str]] = [] 
    506        self._parts: list[RulePart] = [] 
    507 
    508    def empty(self) -> Rule: 
    509        """ 
    510        Return an unbound copy of this rule. 
    511 
    512        This can be useful if want to reuse an already bound URL for another 
    513        map.  See ``get_empty_kwargs`` to override what keyword arguments are 
    514        provided to the new copy. 
    515        """ 
    516        return type(self)(self.rule, **self.get_empty_kwargs()) 
    517 
    518    def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: 
    519        """ 
    520        Provides kwargs for instantiating empty copy with empty() 
    521 
    522        Use this method to provide custom keyword arguments to the subclass of 
    523        ``Rule`` when calling ``some_rule.empty()``.  Helpful when the subclass 
    524        has custom keyword arguments that are needed at instantiation. 
    525 
    526        Must return a ``dict`` that will be provided as kwargs to the new 
    527        instance of ``Rule``, following the initial ``self.rule`` value which 
    528        is always provided as the first, required positional argument. 
    529        """ 
    530        defaults = None 
    531        if self.defaults: 
    532            defaults = dict(self.defaults) 
    533        return dict( 
    534            defaults=defaults, 
    535            subdomain=self.subdomain, 
    536            methods=self.methods, 
    537            build_only=self.build_only, 
    538            endpoint=self.endpoint, 
    539            strict_slashes=self.strict_slashes, 
    540            redirect_to=self.redirect_to, 
    541            alias=self.alias, 
    542            host=self.host, 
    543        ) 
    544 
    545    def get_rules(self, map: Map) -> t.Iterator[Rule]: 
    546        yield self 
    547 
    548    def refresh(self) -> None: 
    549        """Rebinds and refreshes the URL.  Call this if you modified the 
    550        rule in place. 
    551 
    552        :internal: 
    553        """ 
    554        self.bind(self.map, rebind=True) 
    555 
    556    def bind(self, map: Map, rebind: bool = False) -> None: 
    557        """Bind the url to a map and create a regular expression based on 
    558        the information from the rule itself and the defaults from the map. 
    559 
    560        :internal: 
    561        """ 
    562        if self.map is not None and not rebind: 
    563            raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") 
    564        self.map = map 
    565        if self.strict_slashes is None: 
    566            self.strict_slashes = map.strict_slashes 
    567        if self.merge_slashes is None: 
    568            self.merge_slashes = map.merge_slashes 
    569        if self.subdomain is None: 
    570            self.subdomain = map.default_subdomain 
    571        self.compile() 
    572 
    573    def get_converter( 
    574        self, 
    575        variable_name: str, 
    576        converter_name: str, 
    577        args: tuple[t.Any, ...], 
    578        kwargs: t.Mapping[str, t.Any], 
    579    ) -> BaseConverter: 
    580        """Looks up the converter for the given parameter. 
    581 
    582        .. versionadded:: 0.9 
    583        """ 
    584        if converter_name not in self.map.converters: 
    585            raise LookupError(f"the converter {converter_name!r} does not exist") 
    586        return self.map.converters[converter_name](self.map, *args, **kwargs) 
    587 
    588    def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: 
    589        items: t.Iterable[tuple[str, str]] = iter_multi_items(query_vars) 
    590 
    591        if self.map.sort_parameters: 
    592            items = sorted(items, key=self.map.sort_key) 
    593 
    594        return _urlencode(items) 
    595 
    596    def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: 
    597        content = "" 
    598        static = True 
    599        argument_weights = [] 
    600        static_weights: list[tuple[int, int]] = [] 
    601        final = False 
    602        convertor_number = 0 
    603 
    604        pos = 0 
    605        while pos < len(rule): 
    606            match = _part_re.match(rule, pos) 
    607            if match is None: 
    608                raise ValueError(f"malformed url rule: {rule!r}") 
    609 
    610            data = match.groupdict() 
    611            if data["static"] is not None: 
    612                static_weights.append((len(static_weights), -len(data["static"]))) 
    613                self._trace.append((False, data["static"])) 
    614                content += data["static"] if static else re.escape(data["static"]) 
    615 
    616            if data["variable"] is not None: 
    617                if static: 
    618                    # Switching content to represent regex, hence the need to escape 
    619                    content = re.escape(content) 
    620                static = False 
    621                c_args, c_kwargs = parse_converter_args(data["arguments"] or "") 
    622                convobj = self.get_converter( 
    623                    data["variable"], data["converter"] or "default", c_args, c_kwargs 
    624                ) 
    625                self._converters[data["variable"]] = convobj 
    626                self.arguments.add(data["variable"]) 
    627                if not convobj.part_isolating: 
    628                    final = True 
    629                content += f"(?P<__werkzeug_{convertor_number}>{convobj.regex})" 
    630                convertor_number += 1 
    631                argument_weights.append(convobj.weight) 
    632                self._trace.append((True, data["variable"])) 
    633 
    634            if data["slash"] is not None: 
    635                self._trace.append((False, "/")) 
    636                if final: 
    637                    content += "/" 
    638                else: 
    639                    if not static: 
    640                        content += r"\Z" 
    641                    weight = Weighting( 
    642                        -len(static_weights), 
    643                        static_weights, 
    644                        -len(argument_weights), 
    645                        argument_weights, 
    646                    ) 
    647                    yield RulePart( 
    648                        content=content, 
    649                        final=final, 
    650                        static=static, 
    651                        suffixed=False, 
    652                        weight=weight, 
    653                    ) 
    654                    content = "" 
    655                    static = True 
    656                    argument_weights = [] 
    657                    static_weights = [] 
    658                    final = False 
    659                    convertor_number = 0 
    660 
    661            pos = match.end() 
    662 
    663        suffixed = False 
    664        if final and content[-1] == "/": 
    665            # If a converter is part_isolating=False (matches slashes) and ends with a 
    666            # slash, augment the regex to support slash redirects. 
    667            suffixed = True 
    668            content = content[:-1] + "(?<!/)(/?)" 
    669        if not static: 
    670            content += r"\Z" 
    671        weight = Weighting( 
    672            -len(static_weights), 
    673            static_weights, 
    674            -len(argument_weights), 
    675            argument_weights, 
    676        ) 
    677        yield RulePart( 
    678            content=content, 
    679            final=final, 
    680            static=static, 
    681            suffixed=suffixed, 
    682            weight=weight, 
    683        ) 
    684        if suffixed: 
    685            yield RulePart( 
    686                content="", final=False, static=True, suffixed=False, weight=weight 
    687            ) 
    688 
    689    def compile(self) -> None: 
    690        """Compiles the regular expression and stores it.""" 
    691        assert self.map is not None, "rule not bound" 
    692 
    693        if self.map.host_matching: 
    694            domain_rule = self.host or "" 
    695        else: 
    696            domain_rule = self.subdomain or "" 
    697        self._parts = [] 
    698        self._trace = [] 
    699        self._converters = {} 
    700        if domain_rule == "": 
    701            self._parts = [ 
    702                RulePart( 
    703                    content="", 
    704                    final=False, 
    705                    static=True, 
    706                    suffixed=False, 
    707                    weight=Weighting(0, [], 0, []), 
    708                ) 
    709            ] 
    710        else: 
    711            self._parts.extend(self._parse_rule(domain_rule)) 
    712        self._trace.append((False, "|")) 
    713        rule = self.rule 
    714        if self.merge_slashes: 
    715            rule = re.sub("/{2,}?", "/", self.rule) 
    716        self._parts.extend(self._parse_rule(rule)) 
    717 
    718        self._build: t.Callable[..., tuple[str, str]] 
    719        self._build = self._compile_builder(False).__get__(self, None) 
    720        self._build_unknown: t.Callable[..., tuple[str, str]] 
    721        self._build_unknown = self._compile_builder(True).__get__(self, None) 
    722 
    723    @staticmethod 
    724    def _get_func_code(code: CodeType, name: str) -> t.Callable[..., tuple[str, str]]: 
    725        globs: dict[str, t.Any] = {} 
    726        locs: dict[str, t.Any] = {} 
    727        exec(code, globs, locs) 
    728        return locs[name]  # type: ignore 
    729 
    730    def _compile_builder( 
    731        self, append_unknown: bool = True 
    732    ) -> t.Callable[..., tuple[str, str]]: 
    733        defaults = self.defaults or {} 
    734        dom_ops: list[tuple[bool, str]] = [] 
    735        url_ops: list[tuple[bool, str]] = [] 
    736 
    737        opl = dom_ops 
    738        for is_dynamic, data in self._trace: 
    739            if data == "|" and opl is dom_ops: 
    740                opl = url_ops 
    741                continue 
    742            # this seems like a silly case to ever come up but: 
    743            # if a default is given for a value that appears in the rule, 
    744            # resolve it to a constant ahead of time 
    745            if is_dynamic and data in defaults: 
    746                data = self._converters[data].to_url(defaults[data]) 
    747                opl.append((False, data)) 
    748            elif not is_dynamic: 
    749                # safe = https://url.spec.whatwg.org/#url-path-segment-string 
    750                opl.append((False, quote(data, safe="!$&'()*+,/:;=@"))) 
    751            else: 
    752                opl.append((True, data)) 
    753 
    754        def _convert(elem: str) -> ast.stmt: 
    755            ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) 
    756            ret.args = [ast.Name(str(elem), ast.Load())]  # type: ignore  # str for py2 
    757            return ret 
    758 
    759        def _parts(ops: list[tuple[bool, str]]) -> list[ast.AST]: 
    760            parts = [ 
    761                _convert(elem) if is_dynamic else ast.Constant(elem) 
    762                for is_dynamic, elem in ops 
    763            ] 
    764            parts = parts or [ast.Constant("")] 
    765            # constant fold 
    766            ret = [parts[0]] 
    767            for p in parts[1:]: 
    768                if isinstance(p, ast.Constant) and isinstance(ret[-1], ast.Constant): 
    769                    ret[-1] = ast.Constant(ret[-1].value + p.value) 
    770                else: 
    771                    ret.append(p) 
    772            return ret 
    773 
    774        dom_parts = _parts(dom_ops) 
    775        url_parts = _parts(url_ops) 
    776        if not append_unknown: 
    777            body = [] 
    778        else: 
    779            body = [_IF_KWARGS_URL_ENCODE_AST] 
    780            url_parts.extend(_URL_ENCODE_AST_NAMES) 
    781 
    782        def _join(parts: list[ast.AST]) -> ast.AST: 
    783            if len(parts) == 1:  # shortcut 
    784                return parts[0] 
    785            return ast.JoinedStr(parts) 
    786 
    787        body.append( 
    788            ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) 
    789        ) 
    790 
    791        pargs = [ 
    792            elem 
    793            for is_dynamic, elem in dom_ops + url_ops 
    794            if is_dynamic and elem not in defaults 
    795        ] 
    796        kargs = [str(k) for k in defaults] 
    797 
    798        func_ast: ast.FunctionDef = _prefix_names("def _(): pass")  # type: ignore 
    799        func_ast.name = f"<builder:{self.rule!r}>" 
    800        func_ast.args.args.append(ast.arg(".self", None)) 
    801        for arg in pargs + kargs: 
    802            func_ast.args.args.append(ast.arg(arg, None)) 
    803        func_ast.args.kwarg = ast.arg(".kwargs", None) 
    804        for _ in kargs: 
    805            func_ast.args.defaults.append(ast.Constant("")) 
    806        func_ast.body = body 
    807 
    808        # Use `ast.parse` instead of `ast.Module` for better portability, since the 
    809        # signature of `ast.Module` can change. 
    810        module = ast.parse("") 
    811        module.body = [func_ast] 
    812 
    813        # mark everything as on line 1, offset 0 
    814        # less error-prone than `ast.fix_missing_locations` 
    815        # bad line numbers cause an assert to fail in debug builds 
    816        for node in ast.walk(module): 
    817            if "lineno" in node._attributes: 
    818                node.lineno = 1 
    819            if "end_lineno" in node._attributes: 
    820                node.end_lineno = node.lineno 
    821            if "col_offset" in node._attributes: 
    822                node.col_offset = 0 
    823            if "end_col_offset" in node._attributes: 
    824                node.end_col_offset = node.col_offset 
    825 
    826        code = compile(module, "<werkzeug routing>", "exec") 
    827        return self._get_func_code(code, func_ast.name) 
    828 
    829    def build( 
    830        self, values: t.Mapping[str, t.Any], append_unknown: bool = True 
    831    ) -> tuple[str, str] | None: 
    832        """Assembles the relative url for that rule and the subdomain. 
    833        If building doesn't work for some reasons `None` is returned. 
    834 
    835        :internal: 
    836        """ 
    837        try: 
    838            if append_unknown: 
    839                return self._build_unknown(**values) 
    840            else: 
    841                return self._build(**values) 
    842        except ValidationError: 
    843            return None 
    844 
    845    def provides_defaults_for(self, rule: Rule) -> bool: 
    846        """Check if this rule has defaults for a given rule. 
    847 
    848        :internal: 
    849        """ 
    850        return bool( 
    851            not self.build_only 
    852            and self.defaults 
    853            and self.endpoint == rule.endpoint 
    854            and self != rule 
    855            and self.arguments == rule.arguments 
    856        ) 
    857 
    858    def suitable_for( 
    859        self, values: t.Mapping[str, t.Any], method: str | None = None 
    860    ) -> bool: 
    861        """Check if the dict of values has enough data for url generation. 
    862 
    863        :internal: 
    864        """ 
    865        # if a method was given explicitly and that method is not supported 
    866        # by this rule, this rule is not suitable. 
    867        if ( 
    868            method is not None 
    869            and self.methods is not None 
    870            and method not in self.methods 
    871        ): 
    872            return False 
    873 
    874        defaults = self.defaults or () 
    875 
    876        # all arguments required must be either in the defaults dict or 
    877        # the value dictionary otherwise it's not suitable 
    878        for key in self.arguments: 
    879            if key not in defaults and key not in values: 
    880                return False 
    881 
    882        # in case defaults are given we ensure that either the value was 
    883        # skipped or the value is the same as the default value. 
    884        if defaults: 
    885            for key, value in defaults.items(): 
    886                if key in values and value != values[key]: 
    887                    return False 
    888 
    889        return True 
    890 
    891    def build_compare_key(self) -> tuple[int, int, int]: 
    892        """The build compare key for sorting. 
    893 
    894        :internal: 
    895        """ 
    896        return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) 
    897 
    898    def __eq__(self, other: object) -> bool: 
    899        return isinstance(other, type(self)) and self._trace == other._trace 
    900 
    901    __hash__ = None  # type: ignore 
    902 
    903    def __str__(self) -> str: 
    904        return self.rule 
    905 
    906    def __repr__(self) -> str: 
    907        if self.map is None: 
    908            return f"<{type(self).__name__} (unbound)>" 
    909        parts = [] 
    910        for is_dynamic, data in self._trace: 
    911            if is_dynamic: 
    912                parts.append(f"<{data}>") 
    913            else: 
    914                parts.append(data) 
    915        parts_str = "".join(parts).lstrip("|") 
    916        methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" 
    917        return f"<{type(self).__name__} {parts_str!r}{methods} -> {self.endpoint}>"