From 39e0eb5d6d6ca9fa5b398463a353b19456a27511 Mon Sep 17 00:00:00 2001 From: Ken Kinder Date: Sun, 16 Jun 2024 15:00:07 +0100 Subject: [PATCH] Fix micropython issue where urls were not being parsed --- examples/tutorial/10_full_app/pages.py | 2 + puepy/router.py | 58 +++++++++++++++++++++++++- tests/test_router.py | 47 ++++++++++++++++++++- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/examples/tutorial/10_full_app/pages.py b/examples/tutorial/10_full_app/pages.py index 87dab11..6592f26 100644 --- a/examples/tutorial/10_full_app/pages.py +++ b/examples/tutorial/10_full_app/pages.py @@ -128,6 +128,8 @@ def populate(self): t.sl_button("Login", type="submit") t.p("Use any username or password") + t.p(f"You will be returned to {self.return_to}") + def on_submit(self, event): event.preventDefault() if self.refs["form"].element.checkValidity(): diff --git a/puepy/router.py b/puepy/router.py index 0e68592..ec1a7f2 100644 --- a/puepy/router.py +++ b/puepy/router.py @@ -3,6 +3,57 @@ from .util import mixed_to_underscores, jsobj +def _micropython_parse_query_string(query_string): + """ + In MicroPython, urllib isn't available and we can't use the JavaScript library: + https://github.com/pyscript/pyscript/issues/2100 + """ + if query_string and query_string[0] == "?": + query_string = query_string[1:] + + def url_decode(s): + # Decode URL-encoded characters without using regex, which is also pretty broken in MicroPython... + i = 0 + length = len(s) + decoded = [] + + while i < length: + if s[i] == "%": + if i + 2 < length: + hex_value = s[i + 1 : i + 3] + decoded.append(chr(int(hex_value, 16))) + i += 3 + else: + decoded.append("%") + i += 1 + elif s[i] == "+": + decoded.append(" ") + i += 1 + else: + decoded.append(s[i]) + i += 1 + + return "".join(decoded) + + params = {} + for part in query_string.split("&"): + if "=" in part: + key, value = part.split("=", 1) + key = url_decode(key) + value = url_decode(value) + if key in params: + params[key].append(value) + else: + params[key] = [value] + else: + key = url_decode(part) + if key in params: + params[key].append("") + else: + params[key] = "" + return params + + if platform == PLATFORM_MICROPYTHON: from js import encodeURIComponent @@ -18,6 +69,9 @@ def url_quote(s): def parse_query_string(qs): return parse_qs(urlparse(qs).query) +elif platform == PLATFORM_MICROPYTHON: + parse_query_string = _micropython_parse_query_string + else: import js @@ -142,8 +196,8 @@ def match(self, path): def navigate_to_path(self, path, **kwargs): if isinstance(path, type) and issubclass(path, Page): path = self.reverse(path, **kwargs) - else: - path = path + "?" + "&".join(f"{url_quote(k)}={url_quote(v)}" for k, v in kwargs.items()) + elif kwargs: + path += "?" + "&".join(f"{url_quote(k)}={url_quote(v)}" for k, v in kwargs.items()) if self.link_mode == self.LINK_MODE_DIRECT: window.location = path diff --git a/tests/test_router.py b/tests/test_router.py index 04a8c26..84c5e13 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -1,7 +1,7 @@ import unittest from puepy.core import Page -from puepy.router import Router, Route +from puepy.router import Router, Route, _micropython_parse_query_string class TestRoute(unittest.TestCase): @@ -90,5 +90,50 @@ def test_no_match_route(self): self.assertIsNone(params) +class TestMicropythonParseQueryString(unittest.TestCase): + def test_simple_query(self): + query_string = "?name=John" + expected_output = {"name": ["John"]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_multiple_params(self): + query_string = "?name=John&age=30" + expected_output = {"name": ["John"], "age": ["30"]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_url_encoded_chars(self): + query_string = "?name=John%20Doe&age=30" + expected_output = {"name": ["John Doe"], "age": ["30"]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_repeated_params(self): + query_string = "?name=John&name=Jane" + expected_output = {"name": ["John", "Jane"]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_no_value_param(self): + query_string = "?name=" + expected_output = {"name": [""]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_no_value_multiple_params(self): + query_string = "?name=&age=" + expected_output = {"name": [""], "age": [""]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_plus_as_space(self): + query_string = "?name=John+Doe&age=30" + expected_output = {"name": ["John Doe"], "age": ["30"]} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + def test_single_param_without_value(self): + query_string = "?name" + expected_output = {"name": ""} + self.assertEqual(_micropython_parse_query_string(query_string), expected_output) + + +if __name__ == "__main__": + unittest.main() + if __name__ == "__main__": unittest.main()