Skip to content

Commit 412f75d

Browse files
authored
Only unquote on string casts (#74)
* Only unquote on string casts * line length * Add unit tests * squash * Fix typing issue
1 parent e840866 commit 412f75d

File tree

4 files changed

+65
-5
lines changed

4 files changed

+65
-5
lines changed

sanic_routing/route.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(
7474
name: str,
7575
handler: t.Callable[..., t.Any],
7676
methods: t.Union[t.Sequence[str], t.FrozenSet[str]],
77-
requirements: t.Dict[str, t.Any] = None,
77+
requirements: t.Optional[t.Dict[str, t.Any]] = None,
7878
strict: bool = False,
7979
unquote: bool = False,
8080
static: bool = False,

sanic_routing/tree.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from .group import RouteGroup
55
from .line import Line
6-
from .patterns import REGEX_PARAM_NAME, REGEX_PARAM_NAME_EXT
6+
from .patterns import REGEX_PARAM_NAME, REGEX_PARAM_NAME_EXT, alpha, ext, slug
77

88
logger = getLogger("sanic.root")
99

@@ -16,6 +16,7 @@ def __init__(
1616
parent=None,
1717
router=None,
1818
param=None,
19+
unquote=False,
1920
) -> None:
2021
self.root = root
2122
self.part = part
@@ -34,7 +35,7 @@ def __init__(
3435
self.children_param_injected = False
3536
self.has_deferred = False
3637
self.equality_check = False
37-
self.unquote = False
38+
self.unquote = unquote
3839
self.router = router
3940

4041
def __str__(self) -> str:
@@ -268,7 +269,7 @@ def _inject_param_check(self, location, indent, idx):
268269
Line("pass", indent + 1),
269270
Line("else:", indent),
270271
]
271-
if self.unquote:
272+
if self.unquote and self._cast_as_str(self.param.cast):
272273
lines.append(
273274
Line(
274275
f"basket['__matches__'][{idx}] = "
@@ -280,6 +281,11 @@ def _inject_param_check(self, location, indent, idx):
280281

281282
location.extend(lines)
282283

284+
@staticmethod
285+
def _cast_as_str(cast) -> bool:
286+
return_type_hint = t.get_type_hints(cast).get("return")
287+
return cast in (str, ext, slug, alpha) or return_type_hint is str
288+
283289
@staticmethod
284290
def _inject_method_check(location, indent, group):
285291
"""
@@ -436,6 +442,7 @@ def generate(self, groups: t.Iterable[RouteGroup]) -> None:
436442
"""
437443
for group in groups:
438444
current = self.root
445+
current.unquote = current.unquote or group.unquote
439446
for level, part in enumerate(group.parts):
440447
param = None
441448
dynamic = part.startswith("<")
@@ -452,14 +459,14 @@ def generate(self, groups: t.Iterable[RouteGroup]) -> None:
452459
parent=current,
453460
router=self.router,
454461
param=param,
462+
unquote=current.unquote,
455463
)
456464
child.dynamic = dynamic
457465
current.add_child(child)
458466
current = current._children[part]
459467
current.level = level + 1
460468

461469
current.groups.append(group)
462-
current.unquote = current.unquote or group.unquote
463470

464471
def display(self) -> None:
465472
"""

tests/test_builtin_param_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from unittest.mock import Mock
22

33
import pytest
4+
45
from sanic_routing import BaseRouter
56
from sanic_routing.exceptions import InvalidUsage, NotFound
67

tests/test_unquote.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from unittest.mock import Mock
2+
3+
from sanic_routing import BaseRouter
4+
5+
6+
class Router(BaseRouter):
7+
def get(self, path, method, extra=None):
8+
return self.resolve(path=path, method=method, extra=extra)
9+
10+
11+
def test_no_unquote():
12+
handler = Mock(return_value=123)
13+
14+
router = Router()
15+
router.add("/<foo>/<bar>", methods=["GET"], handler=handler, unquote=False)
16+
router.finalize()
17+
18+
_, handler, params = router.get("/%F0%9F%98%8E/sunglasses", "GET")
19+
assert params == {"bar": "sunglasses", "foo": "%F0%9F%98%8E"}
20+
21+
_, handler, params = router.get("/😎/sunglasses", "GET")
22+
assert params == {"bar": "sunglasses", "foo": "😎"}
23+
24+
25+
def test_unquote():
26+
handler = Mock(return_value=123)
27+
28+
router = Router()
29+
router.add("/<foo>/<bar>", methods=["GET"], handler=handler, unquote=True)
30+
router.finalize()
31+
32+
_, handler, params = router.get("/%F0%9F%98%8E/sunglasses", "GET")
33+
assert params == {"bar": "sunglasses", "foo": "😎"}
34+
35+
_, handler, params = router.get("/😎/sunglasses", "GET")
36+
assert params == {"bar": "sunglasses", "foo": "😎"}
37+
38+
39+
def test_unquote_non_string():
40+
handler = Mock(return_value=123)
41+
42+
router = Router()
43+
router.add(
44+
"/<foo>/<bar:int>", methods=["GET"], handler=handler, unquote=True
45+
)
46+
router.finalize()
47+
48+
_, handler, params = router.get("/%F0%9F%98%8E/123", "GET")
49+
assert params == {"bar": 123, "foo": "😎"}
50+
51+
_, handler, params = router.get("/😎/123", "GET")
52+
assert params == {"bar": 123, "foo": "😎"}

0 commit comments

Comments
 (0)