Cython routing added
Lint Code / lint (push) Failing after 44s
Run Tests / test (3.12) (push) Successful in 3m48s
Run Tests / test (3.13) (push) Successful in 3m7s
CI/CD Pipeline / test (push) Successful in 1s
CI/CD Pipeline / build-and-release (push) Has been skipped
CI/CD Pipeline / notify (push) Successful in 1s
CI/CD Pipeline / lint (push) Successful in 0s
Lint Code / lint (push) Failing after 44s
Run Tests / test (3.12) (push) Successful in 3m48s
Run Tests / test (3.13) (push) Successful in 3m7s
CI/CD Pipeline / test (push) Successful in 1s
CI/CD Pipeline / build-and-release (push) Has been skipped
CI/CD Pipeline / notify (push) Successful in 1s
CI/CD Pipeline / lint (push) Successful in 0s
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user