77* Author(s): Dan Halbert, Michał Pokusa
88"""
99
10+ try :
11+ from typing import Callable , List , Union , Tuple
12+ except ImportError :
13+ pass
14+
15+ import re
16+
1017from .methods import HTTPMethod
1118
1219
@@ -15,14 +22,110 @@ class _HTTPRoute:
1522
1623 def __init__ (self , path : str = "" , method : HTTPMethod = HTTPMethod .GET ) -> None :
1724
18- self .path = path
25+ contains_parameters = re .search (r"<\w*>" , path ) is not None
26+
27+ self .path = (
28+ path if not contains_parameters else re .sub (r"<\w*>" , r"([^/]*)" , path )
29+ )
1930 self .method = method
31+ self ._contains_parameters = contains_parameters
32+
33+ def match (self , other : "_HTTPRoute" ) -> Tuple [bool , List [str ]]:
34+ """
35+ Checks if the route matches the other route.
36+
37+ If the route contains parameters, it will check if the ``other`` route contains values for
38+ them.
39+
40+ Returns tuple of a boolean and a list of strings. The boolean indicates if the routes match,
41+ and the list contains the values of the url parameters from the ``other`` route.
42+
43+ Examples::
44+
45+ route = _HTTPRoute("/example", HTTPMethod.GET)
46+
47+ other1 = _HTTPRoute("/example", HTTPMethod.GET)
48+ route.matches(other1) # True, []
49+
50+ other2 = _HTTPRoute("/other-example", HTTPMethod.GET)
51+ route.matches(other2) # False, []
52+
53+ ...
54+
55+ route = _HTTPRoute("/example/<parameter>", HTTPMethod.GET)
56+
57+ other1 = _HTTPRoute("/example/123", HTTPMethod.GET)
58+ route.matches(other1) # True, ["123"]
59+
60+ other2 = _HTTPRoute("/other-example", HTTPMethod.GET)
61+ route.matches(other2) # False, []
62+ """
63+
64+ if self .method != other .method :
65+ return False , []
66+
67+ if not self ._contains_parameters :
68+ return self .path == other .path , []
69+
70+ regex_match = re .match (self .path , other .path )
71+ if regex_match is None :
72+ return False , []
73+
74+ return True , regex_match .groups ()
75+
76+ def __repr__ (self ) -> str :
77+ return f"_HTTPRoute(path={ repr (self .path )} , method={ repr (self .method )} )"
78+
79+
80+ class _HTTPRoutes :
81+ """A collection of routes and their corresponding handlers."""
82+
83+ def __init__ (self ) -> None :
84+ self ._routes : List [_HTTPRoute ] = []
85+ self ._handlers : List [Callable ] = []
86+
87+ def add (self , route : _HTTPRoute , handler : Callable ):
88+ """Adds a route and its handler to the collection."""
89+
90+ self ._routes .append (route )
91+ self ._handlers .append (handler )
92+
93+ def find_handler (self , route : _HTTPRoute ) -> Union [Callable , None ]:
94+ """
95+ Finds a handler for a given route.
96+
97+ If route used URL parameters, the handler will be wrapped to pass the parameters to the
98+ handler.
99+
100+ Example::
101+
102+ @server.route("/example/<my_parameter>", HTTPMethod.GET)
103+ def route_func(request, my_parameter):
104+ ...
105+ request.path == "/example/123" # True
106+ my_parameter == "123" # True
107+ """
108+ if not self ._routes :
109+ raise ValueError ("No routes added" )
110+
111+ found_route , _route = False , None
112+
113+ for _route in self ._routes :
114+ matches , url_parameters_values = _route .match (route )
115+
116+ if matches :
117+ found_route = True
118+ break
119+
120+ if not found_route :
121+ return None
122+
123+ handler = self ._handlers [self ._routes .index (_route )]
20124
21- def __hash__ ( self ) -> int :
22- return hash ( self . method ) ^ hash ( self . path )
125+ def wrapped_handler ( request ) :
126+ return handler ( request , * url_parameters_values )
23127
24- def __eq__ (self , other : "_HTTPRoute" ) -> bool :
25- return self .method == other .method and self .path == other .path
128+ return wrapped_handler
26129
27130 def __repr__ (self ) -> str :
28- return f"HTTPRoute(path= { repr (self .path ) } , method= { repr ( self . method )} )"
131+ return f"_HTTPRoutes( { repr (self ._routes )} )"
0 commit comments