Skip to content

Commit 17ea206

Browse files
committed
Refactored _Routes to Server method
1 parent 4002f26 commit 17ea206

File tree

2 files changed

+67
-84
lines changed

2 files changed

+67
-84
lines changed

adafruit_httpserver/route.py

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
try:
11-
from typing import Callable, List, Iterable, Union, Tuple, Dict, TYPE_CHECKING
11+
from typing import Callable, Iterable, Union, Tuple, Literal, Dict, TYPE_CHECKING
1212

1313
if TYPE_CHECKING:
1414
from .response import Response
@@ -36,13 +36,18 @@ def __init__(
3636
self.parameters_names = [
3737
name[1:-1] for name in re.compile(r"/[^<>]*/?").split(path) if name != ""
3838
]
39-
self.path = re.sub(r"<\w+>", r"([^/]+)", path).replace("....", r".+").replace(
40-
"...", r"[^/]+"
41-
) + ("/?" if append_slash else "")
39+
self.path_pattern = re.compile(
40+
r"^"
41+
+ re.sub(r"<\w+>", r"([^/]+)", path)
42+
.replace(r"....", r".+")
43+
.replace(r"...", r"[^/]+")
44+
+ (r"/?" if append_slash else r"")
45+
+ r"$"
46+
)
47+
self.path = path
4248
self.methods = (
4349
set(methods) if isinstance(methods, (set, list, tuple)) else set([methods])
4450
)
45-
4651
self.handler = handler
4752

4853
@staticmethod
@@ -56,7 +61,9 @@ def _validate_path(path: str, append_slash: bool) -> None:
5661
if path.endswith("/") and append_slash:
5762
raise ValueError("Cannot use append_slash=True when path ends with /")
5863

59-
def match(self, other: "Route") -> Tuple[bool, Dict[str, str]]:
64+
def matches(
65+
self, method: str, path: str
66+
) -> Union[Tuple[Literal[False], None], Tuple[Literal[True], Dict[str, str]]]:
6067
"""
6168
Checks if the route matches the other route.
6269
@@ -68,45 +75,41 @@ def match(self, other: "Route") -> Tuple[bool, Dict[str, str]]:
6875
6976
Examples::
7077
71-
route = Route("/example", GET, True)
78+
route = Route("/example", GET, append_slash=True)
7279
73-
other1a = Route("/example", GET)
74-
other1b = Route("/example/", GET)
75-
route.matches(other1a) # True, {}
76-
route.matches(other1b) # True, {}
80+
route.matches(GET, "/example") # True, {}
81+
route.matches(GET, "/example/") # True, {}
7782
78-
other2 = Route("/other-example", GET)
79-
route.matches(other2) # False, {}
83+
route.matches(GET, "/other-example") # False, None
84+
route.matches(POST, "/example/") # False, None
8085
8186
...
8287
8388
route = Route("/example/<parameter>", GET)
8489
85-
other1 = Route("/example/123", GET)
86-
route.matches(other1) # True, {"parameter": "123"}
90+
route.matches(GET, "/example/123") # True, {"parameter": "123"}
8791
88-
other2 = Route("/other-example", GET)
89-
route.matches(other2) # False, {}
92+
route.matches(GET, "/other-example") # False, None
9093
9194
...
9295
93-
route1 = Route("/example/.../something", GET)
94-
other1 = Route("/example/123/something", GET)
95-
route1.matches(other1) # True, {}
96+
route = Route("/example/.../something", GET)
97+
route.matches(GET, "/example/123/something") # True, {}
9698
97-
route2 = Route("/example/..../something", GET)
98-
other2 = Route("/example/123/456/something", GET)
99-
route2.matches(other2) # True, {}
99+
route = Route("/example/..../something", GET)
100+
route.matches(GET, "/example/123/456/something") # True, {}
100101
"""
101102

102-
if not other.methods.issubset(self.methods):
103-
return False, {}
103+
if method not in self.methods:
104+
return False, None
105+
106+
path_match = self.path_pattern.match(path)
107+
if path_match is None:
108+
return False, None
104109

105-
regex_match = re.match(f"^{self.path}$", other.path)
106-
if regex_match is None:
107-
return False, {}
110+
url_parameters_values = path_match.groups()
108111

109-
return True, dict(zip(self.parameters_names, regex_match.groups()))
112+
return True, dict(zip(self.parameters_names, url_parameters_values))
110113

111114
def __repr__(self) -> str:
112115
path = repr(self.path)
@@ -168,51 +171,3 @@ def route_decorator(func: Callable) -> Route:
168171
return Route(path, methods, func, append_slash=append_slash)
169172

170173
return route_decorator
171-
172-
173-
class _Routes:
174-
"""A collection of routes and their corresponding handlers."""
175-
176-
def __init__(self) -> None:
177-
self._routes: List[Route] = []
178-
179-
def add(self, route: Route):
180-
"""Adds a route and its handler to the collection."""
181-
self._routes.append(route)
182-
183-
def find_handler(self, route: Route) -> Union[Callable["...", "Response"], None]:
184-
"""
185-
Finds a handler for a given route.
186-
187-
If route used URL parameters, the handler will be wrapped to pass the parameters to the
188-
handler.
189-
190-
Example::
191-
192-
@server.route("/example/<my_parameter>", GET)
193-
def route_func(request, my_parameter):
194-
...
195-
request.path == "/example/123" # True
196-
my_parameter == "123" # True
197-
"""
198-
found_route, _route = False, None
199-
200-
for _route in self._routes:
201-
matches, keyword_parameters = _route.match(route)
202-
203-
if matches:
204-
found_route = True
205-
break
206-
207-
if not found_route:
208-
return None
209-
210-
handler = _route.handler
211-
212-
def wrapped_handler(request):
213-
return handler(request, **keyword_parameters)
214-
215-
return wrapped_handler
216-
217-
def __repr__(self) -> str:
218-
return f"_Routes({repr(self._routes)})"

adafruit_httpserver/server.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from .methods import GET, HEAD
3131
from .request import Request
3232
from .response import Response, FileResponse
33-
from .route import _Routes, Route
33+
from .route import Route
3434
from .status import BAD_REQUEST_400, UNAUTHORIZED_401, FORBIDDEN_403, NOT_FOUND_404
3535

3636

@@ -65,7 +65,7 @@ def __init__(
6565
self._auths = []
6666
self._buffer = bytearray(1024)
6767
self._timeout = 1
68-
self._routes = _Routes()
68+
self._routes: "List[Route]" = []
6969
self._socket_source = socket_source
7070
self._sock = None
7171
self.headers = Headers()
@@ -132,7 +132,7 @@ def route_func(request):
132132
"""
133133

134134
def route_decorator(func: Callable) -> Callable:
135-
self._routes.add(Route(path, methods, func, append_slash=append_slash))
135+
self._routes.append(Route(path, methods, func, append_slash=append_slash))
136136
return func
137137

138138
return route_decorator
@@ -157,8 +157,7 @@ def add_routes(self, routes: List[Route]) -> None:
157157
external_route2,
158158
]}
159159
"""
160-
for route in routes:
161-
self._routes.add(route)
160+
self._routes.extend(routes)
162161

163162
def _verify_can_start(self, host: str, port: int) -> None:
164163
"""Check if the server can be successfully started. Raises RuntimeError if not."""
@@ -296,6 +295,35 @@ def _receive_request(
296295

297296
return request
298297

298+
def _find_handler(
299+
self, method: str, path: str
300+
) -> Union[Callable[..., "Response"], None]:
301+
"""
302+
Finds a handler for a given route.
303+
304+
If route used URL parameters, the handler will be wrapped to pass the parameters to the
305+
handler.
306+
307+
Example::
308+
309+
@server.route("/example/<my_parameter>", GET)
310+
def route_func(request, my_parameter):
311+
...
312+
request.path == "/example/123" # True
313+
my_parameter == "123" # True
314+
"""
315+
for route in self._routes:
316+
route_matches, url_parameters = route.matches(method, path)
317+
318+
if route_matches:
319+
320+
def wrapped_handler(request):
321+
return route.handler(request, **url_parameters)
322+
323+
return wrapped_handler
324+
325+
return None
326+
299327
def _handle_request(
300328
self, request: Request, handler: Union[Callable, None]
301329
) -> Union[Response, None]:
@@ -371,8 +399,8 @@ def poll(self) -> str:
371399
conn.close()
372400
return CONNECTION_TIMED_OUT
373401

374-
# Find a handler for the route
375-
handler = self._routes.find_handler(Route(request.path, request.method))
402+
# Find a route that matches the request's method and path and get its handler
403+
handler = self._find_handler(request.method, request.path)
376404

377405
# Handle the request
378406
response = self._handle_request(request, handler)

0 commit comments

Comments
 (0)