""" Pure Python fallback for _routing when PCRE2/Cython is not available. This module provides the same interface using the standard library `re` module. It's slower than the Cython+PCRE2 implementation but works everywhere. In future we may add pcre2.py library support for better performance in this module. """ import re from typing import Any, Dict, List, Optional, Pattern, Tuple class FastRouteMatch: __slots__ = ("config", "params") def __init__(self, config: Dict[str, Any], params: Optional[Dict[str, str]] = None): self.config = config self.params = params if params is not None else {} class FastRouter: """ Router with regex pattern matching. Matching order (nginx-like): 1. Exact routes (prefix "=") - O(1) dict lookup 2. Regex routes (prefix "~" or "~*") - linear scan 3. Default route (fallback) """ __slots__ = ("_exact_routes", "_regex_routes", "_default_route", "_has_default", "_regex_count") def __init__(self) -> None: self._exact_routes: Dict[str, Dict[str, Any]] = {} self._regex_routes: List[Tuple[Pattern[str], Dict[str, Any]]] = [] self._default_route: Dict[str, Any] = {} self._has_default: bool = False self._regex_count: int = 0 def add_route(self, pattern: str, config: Dict[str, Any]) -> None: if pattern.startswith("="): exact_path = pattern[1:] self._exact_routes[exact_path] = config return if pattern == "__default__": self._default_route = config self._has_default = True return if pattern.startswith("~"): case_insensitive = pattern.startswith("~*") regex_pattern = pattern[2:] if case_insensitive else pattern[1:] flags = re.IGNORECASE if case_insensitive else 0 try: compiled_pattern = re.compile(regex_pattern, flags) self._regex_routes.append((compiled_pattern, config)) self._regex_count = len(self._regex_routes) except re.error: pass # Ignore invalid patterns def match(self, path: str) -> Optional[FastRouteMatch]: if path in self._exact_routes: config = self._exact_routes[path] return FastRouteMatch(config, {}) for pattern, config in self._regex_routes: match_obj = pattern.search(path) if match_obj is not None: params = match_obj.groupdict() return FastRouteMatch(config, params) if self._has_default: return FastRouteMatch(self._default_route, {}) return None @property def exact_routes(self) -> Dict[str, Dict[str, Any]]: return self._exact_routes @property def routes(self) -> Dict[Pattern[str], Dict[str, Any]]: return {p: c for p, c in self._regex_routes} @property def default_route(self) -> Optional[Dict[str, Any]]: return self._default_route if self._has_default else None def list_routes(self) -> List[Dict[str, Any]]: result: List[Dict[str, Any]] = [] for path, config in self._exact_routes.items(): result.append({ "type": "exact", "pattern": f"={path}", "config": config, }) for pattern, config in self._regex_routes: result.append({ "type": "regex", "pattern": pattern.pattern, "config": config, }) if self._has_default: result.append({ "type": "default", "pattern": "__default__", "config": self._default_route, }) return result def fast_match(router: FastRouter, path: str) -> Optional[FastRouteMatch]: """ Convenience function for matching a path. Args: router: FastRouter instance path: URL path to match Returns: FastRouteMatch or None """ return router.match(path)