Skip to content

Commit 2ad4a4c

Browse files
committed
Refactor of _Route and _Routes, added append_slash
1 parent a5be235 commit 2ad4a4c

File tree

5 files changed

+46
-17
lines changed

5 files changed

+46
-17
lines changed

adafruit_httpserver/route.py

Lines changed: 14 additions & 6 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,7 +20,12 @@
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 = [
@@ -30,8 +35,9 @@ def __init__(self, path: str = "", method: str = GET) -> None:
3035
path
3136
if not self._contains_parameters
3237
else re.sub(r"<\w*>", r"([^/]*)", path)
33-
)
34-
self.method = method
38+
) + ("/?" if append_slash else "")
39+
self.methods = methods if isinstance(methods, set) else {methods}
40+
self.append_slash = append_slash
3541

3642
@staticmethod
3743
def _validate_path(path: str) -> None:
@@ -76,10 +82,12 @@ def match(self, other: "_Route") -> Tuple[bool, List[str]]:
7682
route.matches(other2) # False, []
7783
"""
7884

79-
if self.method != other.method:
85+
if not other.methods.issubset(self.methods):
8086
return False, []
8187

8288
if not self._contains_parameters:
89+
if self.append_slash:
90+
return self.path == other.path.rstrip("/"), []
8391
return self.path == other.path, []
8492

8593
regex_match = re.match(f"^{self.path}$", other.path)
@@ -89,7 +97,7 @@ def match(self, other: "_Route") -> Tuple[bool, List[str]]:
8997
return True, regex_match.groups()
9098

9199
def __repr__(self) -> str:
92-
return f"_Route(path={repr(self.path)}, method={repr(self.method)})"
100+
return f"_Route(path={repr(self.path)}, methods={repr(self.methods)})"
93101

94102

95103
class _Routes:

adafruit_httpserver/server.py

Lines changed: 27 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,28 @@ 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
6672
:param str path: URL path
6773
:param str methods: HTTP method(s): ``"GET"``, ``"POST"``, ``["GET", "POST"]`` etc.
74+
:param bool append_slash: If True, the route will be accessible with and without a
75+
trailing slash
6876
6977
Example::
7078
@@ -78,6 +86,14 @@ def route_func(request):
7886
def route_func(request):
7987
...
8088
89+
# If you want to access URL with and without trailing slash, use append_slash=True
90+
@server.route("/example-with-slash", append_slash=True)
91+
# which is equivalent to
92+
@server.route("/example-with-slash")
93+
@server.route("/example-with-slash/")
94+
def route_func(request):
95+
...
96+
8197
# Multiple methods can be specified
8298
@server.route("/example", [GET, POST])
8399
def route_func(request):
@@ -88,12 +104,13 @@ def route_func(request):
88104
def route_func(request, my_parameter):
89105
...
90106
"""
91-
if isinstance(methods, str):
92-
methods = [methods]
107+
if path.endswith("/") and append_slash:
108+
raise ValueError("Cannot use append_slash=True when path ends with /")
109+
110+
methods = methods if isinstance(methods, set) else {methods}
93111

94112
def route_decorator(func: Callable) -> Callable:
95-
for method in methods:
96-
self.routes.add(_Route(path, method), func)
113+
self._routes.add(_Route(path, methods, append_slash), func)
97114
return func
98115

99116
return route_decorator
@@ -294,7 +311,9 @@ def poll(self):
294311
_debug_incoming_request(request)
295312

296313
# Find a handler for the route
297-
handler = self.routes.find_handler(_Route(request.path, request.method))
314+
handler = self._routes.find_handler(
315+
_Route(request.path, request.method)
316+
)
298317

299318
# Handle the request
300319
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)