| 31 | |
| 32 | |
| 33 | class StateMachineMatcher: |
| 34 | def __init__(self, merge_slashes: bool) -> None: |
| 35 | self._root = State() |
| 36 | self.merge_slashes = merge_slashes |
| 37 | |
| 38 | def add(self, rule: Rule) -> None: |
| 39 | state = self._root |
| 40 | 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_state |
| 48 | break |
| 49 | else: |
| 50 | new_state = State() |
| 51 | state.dynamic.append((part, new_state)) |
| 52 | state = new_state |
| 53 | state.rules.append(rule) |
| 54 | |
| 55 | def update(self) -> None: |
| 56 | # For every state the dynamic transitions should be sorted by |
| 57 | # the weight of the transition |
| 58 | state = self._root |
| 59 | |
| 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: bool |
| 71 | ) -> tuple[Rule, t.MutableMapping[str, t.Any]]: |
| 72 | # To match to a rule we need to start at the root state and |
| 73 | # try to follow the transitions until we find a match, or find |
| 74 | # there is no transition to follow. |
| 75 | |
| 76 | have_match_for = set() |
| 77 | websocket_mismatch = False |
| 78 | |
| 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 attempt |
| 83 | # to match the head part to the state's transitions. |
| 84 | nonlocal have_match_for, websocket_mismatch |
| 85 | |
| 86 | # The base case is when all parts have been matched via |
| 87 | # transitions. Hence if there is a rule with methods & |
| 88 | # websocket that work return it and the dynamic values |
| 89 | # extracted. |
| 90 | if parts == []: |