Skip to content

Commit 33d6b25

Browse files
committed
Refactor of _Route and _Routes, added append_slash
1 parent a5be235 commit 33d6b25

File tree

5 files changed

+48
-28
lines changed

5 files changed

+48
-28
lines changed

adafruit_httpserver/route.py

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

1010
try:
11-
from typing import Callable, List, Union, Tuple
11+
from typing import Callable, List, Set, Union, Tuple
1212
except ImportError:
1313
pass
1414

@@ -20,18 +20,19 @@
2020
class _Route:
2121
"""Route definition for different paths, see `adafruit_httpserver.server.Server.route`."""
2222

23-
def __init__(self, path: str = "", method: str = GET) -> None:
23+
def __init__(
24+
self,
25+
path: str = "",
26+
methods: Union[str, Set[str]] = GET,
27+
append_slash: bool = False,
28+
) -> None:
2429
self._validate_path(path)
2530

2631
self.parameters_names = [
2732
name[1:-1] for name in re.compile(r"/[^<>]*/?").split(path) if name != ""
2833
]
29-
self.path = (
30-
path
31-
if not self._contains_parameters
32-
else re.sub(r"<\w*>", r"([^/]*)", path)
33-
)
34-
self.method = method
34+
self.path = re.sub(r"<\w*>", r"([^/]*)", path) + ("/?" if append_slash else "")
35+
self.methods = methods if isinstance(methods, set) else {methods}
3536

3637
@staticmethod
3738
def _validate_path(path: str) -> None:
@@ -41,10 +42,6 @@ def _validate_path(path: str) -> None:
4142
if "<>" in path:
4243
raise ValueError("All URL parameters must be named.")
4344

44-
@property
45-
def _contains_parameters(self) -> bool:
46-
return 0 < len(self.parameters_names)
47-
4845
def match(self, other: "_Route") -> Tuple[bool, List[str]]:
4946
"""
5047
Checks if the route matches the other route.
@@ -76,20 +73,20 @@ def match(self, other: "_Route") -> Tuple[bool, List[str]]:
7673
route.matches(other2) # False, []
7774
"""
7875

79-
if self.method != other.method:
76+
if not other.methods.issubset(self.methods):
8077
return False, []
8178

82-
if not self._contains_parameters:
83-
return self.path == other.path, []
84-
8579
regex_match = re.match(f"^{self.path}$", other.path)
8680
if regex_match is None:
8781
return False, []
8882

8983
return True, regex_match.groups()
9084

9185
def __repr__(self) -> str:
92-
return f"_Route(path={repr(self.path)}, method={repr(self.method)})"
86+
path = repr(self.path)
87+
methods = repr(self.methods)
88+
89+
return f"_Route(path={path}, methods={methods})"
9390

9491

9592
class _Routes:

adafruit_httpserver/server.py

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

1010
try:
11-
from typing import Callable, Protocol, Union, List, Tuple
11+
from typing import Callable, Protocol, Union, List, Set, Tuple
1212
from socket import socket
1313
from socketpool import SocketPool
1414
except ImportError:
@@ -51,20 +51,30 @@ def __init__(
5151
self._auths = []
5252
self._buffer = bytearray(1024)
5353
self._timeout = 1
54-
self.routes = _Routes()
54+
self._routes = _Routes()
5555
self._socket_source = socket_source
5656
self._sock = None
5757
self.root_path = root_path
5858
self.stopped = False
5959

6060
self.debug = debug
6161

62-
def route(self, path: str, methods: Union[str, List[str]] = GET) -> Callable:
62+
def route(
63+
self,
64+
path: str,
65+
methods: Union[str, Set[str]] = GET,
66+
*,
67+
append_slash: bool = False,
68+
) -> Callable:
6369
"""
6470
Decorator used to add a route.
6571
72+
If request matches multiple routes, the first matched one added will be used.
73+
6674
:param str path: URL path
6775
:param str methods: HTTP method(s): ``"GET"``, ``"POST"``, ``["GET", "POST"]`` etc.
76+
:param bool append_slash: If True, the route will be accessible with and without a
77+
trailing slash
6878
6979
Example::
7080
@@ -78,6 +88,14 @@ def route_func(request):
7888
def route_func(request):
7989
...
8090
91+
# If you want to access URL with and without trailing slash, use append_slash=True
92+
@server.route("/example-with-slash", append_slash=True)
93+
# which is equivalent to
94+
@server.route("/example-with-slash")
95+
@server.route("/example-with-slash/")
96+
def route_func(request):
97+
...
98+
8199
# Multiple methods can be specified
82100
@server.route("/example", [GET, POST])
83101
def route_func(request):
@@ -88,12 +106,13 @@ def route_func(request):
88106
def route_func(request, my_parameter):
89107
...
90108
"""
91-
if isinstance(methods, str):
92-
methods = [methods]
109+
if path.endswith("/") and append_slash:
110+
raise ValueError("Cannot use append_slash=True when path ends with /")
111+
112+
methods = methods if isinstance(methods, set) else {methods}
93113

94114
def route_decorator(func: Callable) -> Callable:
95-
for method in methods:
96-
self.routes.add(_Route(path, method), func)
115+
self._routes.add(_Route(path, methods, append_slash), func)
97116
return func
98117

99118
return route_decorator
@@ -294,7 +313,9 @@ def poll(self):
294313
_debug_incoming_request(request)
295314

296315
# Find a handler for the route
297-
handler = self.routes.find_handler(_Route(request.path, request.method))
316+
handler = self._routes.find_handler(
317+
_Route(request.path, request.method)
318+
)
298319

299320
# Handle the request
300321
self._handle_request(request, handler)

docs/examples.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ You can pass a list of methods or a single method as a string.
101101

102102
It is recommended to use the the values in ``adafruit_httpserver.methods`` module to avoid typos and for future proofness.
103103

104-
In example below, handler for ``/api`` route will be called when any of ``GET``, ``POST``, ``PUT``, ``DELETE`` methods is used.
104+
If you want to route a given path with and without trailing slash, use ``append_slash=True`` parameter.
105+
106+
In example below, handler for ``/api`` and ``/api/`` route will be called when any of ``GET``, ``POST``, ``PUT``, ``DELETE`` methods is used.
105107

106108
.. literalinclude:: ../examples/httpserver_methods.py
107109
:caption: examples/httpserver_methods.py

examples/httpserver_cpu_information.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
server = Server(pool, debug=True)
1414

1515

16-
@server.route("/cpu-information")
16+
@server.route("/cpu-information", append_slash=True)
1717
def cpu_information_handler(request: Request):
1818
"""
1919
Return the current CPU temperature, frequency, and voltage as JSON.

examples/httpserver_methods.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
server = Server(pool, debug=True)
1313

1414

15-
@server.route("/api", [GET, POST, PUT, DELETE])
15+
@server.route("/api", [GET, POST, PUT, DELETE], append_slash=True)
1616
def api(request: Request):
1717
"""
1818
Performs different operations depending on the HTTP method.

0 commit comments

Comments
 (0)