Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
matcher.py203 linesDownload Raw Back to routing
1from __future__ import annotations2 3import re4import typing as t5from dataclasses import dataclass6from dataclasses import field7 8from .converters import ValidationError9from .exceptions import NoMatch10from .exceptions import RequestAliasRedirect11from .exceptions import RequestPath12from .rules import Rule13from .rules import RulePart14 15 16class SlashRequired(Exception):17    pass18 19 20@dataclass21class State:22    """A representation of a rule state.23 24    This includes the *rules* that correspond to the state and the25    possible *static* and *dynamic* transitions to the next state.26    """27 28    dynamic: list[tuple[RulePart, State]] = field(default_factory=list)29    rules: list[Rule] = field(default_factory=list)30    static: dict[str, State] = field(default_factory=dict)31 32 33class StateMachineMatcher:34    def __init__(self, merge_slashes: bool) -> None:35        self._root = State()36        self.merge_slashes = merge_slashes37 38    def add(self, rule: Rule) -> None:39        state = self._root40        for part in rule._parts:41            if part.static:42                state.static.setdefault(part.content, State())43                state = state.static[part.content]44            else:45                for test_part, new_state in state.dynamic:46                    if test_part == part:47                        state = new_state48                        break49                else:50                    new_state = State()51                    state.dynamic.append((part, new_state))52                    state = new_state53        state.rules.append(rule)54 55    def update(self) -> None:56        # For every state the dynamic transitions should be sorted by57        # the weight of the transition58        state = self._root59 60        def _update_state(state: State) -> None:61            state.dynamic.sort(key=lambda entry: entry[0].weight)62            for new_state in state.static.values():63                _update_state(new_state)64            for _, new_state in state.dynamic:65                _update_state(new_state)66 67        _update_state(state)68 69    def match(70        self, domain: str, path: str, method: str, websocket: bool71    ) -> tuple[Rule, t.MutableMapping[str, t.Any]]:72        # To match to a rule we need to start at the root state and73        # try to follow the transitions until we find a match, or find74        # there is no transition to follow.75 76        have_match_for = set()77        websocket_mismatch = False78 79        def _match(80            state: State, parts: list[str], values: list[str]81        ) -> tuple[Rule, list[str]] | None:82            # This function is meant to be called recursively, and will attempt83            # to match the head part to the state's transitions.84            nonlocal have_match_for, websocket_mismatch85 86            # The base case is when all parts have been matched via87            # transitions. Hence if there is a rule with methods &88            # websocket that work return it and the dynamic values89            # extracted.90            if parts == []:91                for rule in state.rules:92                    if rule.methods is not None and method not in rule.methods:93                        have_match_for.update(rule.methods)94                    elif rule.websocket != websocket:95                        websocket_mismatch = True96                    else:97                        return rule, values98 99                # Test if there is a match with this path with a100                # trailing slash, if so raise an exception to report101                # that matching is possible with an additional slash102                if "" in state.static:103                    for rule in state.static[""].rules:104                        if websocket == rule.websocket and (105                            rule.methods is None or method in rule.methods106                        ):107                            if rule.strict_slashes:108                                raise SlashRequired()109                            else:110                                return rule, values111                return None112 113            part = parts[0]114            # To match this part try the static transitions first115            if part in state.static:116                rv = _match(state.static[part], parts[1:], values)117                if rv is not None:118                    return rv119            # No match via the static transitions, so try the dynamic120            # ones.121            for test_part, new_state in state.dynamic:122                target = part123                remaining = parts[1:]124                # A final part indicates a transition that always125                # consumes the remaining parts i.e. transitions to a126                # final state.127                if test_part.final:128                    target = "/".join(parts)129                    remaining = []130                match = re.compile(test_part.content).match(target)131                if match is not None:132                    if test_part.suffixed:133                        # If a part_isolating=False part has a slash suffix, remove the134                        # suffix from the match and check for the slash redirect next.135                        suffix = match.groups()[-1]136                        if suffix == "/":137                            remaining = [""]138 139                    converter_groups = sorted(140                        match.groupdict().items(), key=lambda entry: entry[0]141                    )142                    groups = [143                        value144                        for key, value in converter_groups145                        if key[:11] == "__werkzeug_"146                    ]147                    rv = _match(new_state, remaining, values + groups)148                    if rv is not None:149                        return rv150 151            # If there is no match and the only part left is a152            # trailing slash ("") consider rules that aren't153            # strict-slashes as these should match if there is a final154            # slash part.155            if parts == [""]:156                for rule in state.rules:157                    if rule.strict_slashes:158                        continue159                    if rule.methods is not None and method not in rule.methods:160                        have_match_for.update(rule.methods)161                    elif rule.websocket != websocket:162                        websocket_mismatch = True163                    else:164                        return rule, values165 166            return None167 168        try:169            rv = _match(self._root, [domain, *path.split("/")], [])170        except SlashRequired:171            raise RequestPath(f"{path}/") from None172 173        if self.merge_slashes and rv is None:174            # Try to match again, but with slashes merged175            path = re.sub("/{2,}", "/", path)176            try:177                rv = _match(self._root, [domain, *path.split("/")], [])178            except SlashRequired:179                raise RequestPath(f"{path}/") from None180            if rv is None or rv[0].merge_slashes is False:181                raise NoMatch(have_match_for, websocket_mismatch)182            else:183                raise RequestPath(f"{path}")184        elif rv is not None:185            rule, values = rv186 187            result = {}188            for name, value in zip(rule._converters.keys(), values):189                try:190                    value = rule._converters[name].to_python(value)191                except ValidationError:192                    raise NoMatch(have_match_for, websocket_mismatch) from None193                result[str(name)] = value194            if rule.defaults:195                result.update(rule.defaults)196 197            if rule.alias and rule.map.redirect_defaults:198                raise RequestAliasRedirect(result, rule.endpoint)199 200            return rule, result201 202        raise NoMatch(have_match_for, websocket_mismatch)203 
codekingpro/portable-devtools · Team Ai