10000 PyDom compatibility with MicroPython (#1954) · sugruedes/pyscript@bcaab0e · GitHub
[go: up one dir, main page]

Skip to content

Commit bcaab0e

Browse files
PyDom compatibility with MicroPython (pyscript#1954)
* fix pydom example * fix the pydom test example to use a python syntax that works with MicroPython by replacing datetime * add note about capturing errors importing when * patch event_handler to handle compat with micropython * turn pyweb into a package and remove hack to make pydom a sort of module with an ugly hack * add pydom example using micropython * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix select element test * change pydom test page to let pytest tests load it properly * add missing folders to test dev server so it can run examples in the manual tests folder * add pydom tests to the test suite as integration tests * lint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * improve fixes in event_handling * change when decorator to actually dynamically fail in micropython and support handlers with or without arguments * simplify when decorator code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add type declaration back for the MP use case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed code to access pydom get index as I can't think of any proper use case * remove old commented hack to replace pydom module with class * fix examples title * precommit checks * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3ff0f84 commit bcaab0e

File tree

12 files changed

+149
-36
lines changed

12 files changed

+149
-36
lines changed

pyscript.core/src/stdlib/pyscript/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
try:
4444
from pyscript.event_handling import when
4545
except:
46+
# TODO: should we remove this? Or at the very least, we should capture
47+
# the traceback otherwise it's very hard to debug
4648
from pyscript.util import NotSupported
4749

4850
when = NotSupported(

pyscript.core/src/stdlib/pyscript/event_handling.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import inspect
22

3-
from pyodide.ffi.wrappers import add_event_listener
3+
try:
4+
from pyodide.ffi.wrappers import add_event_listener
5+
6+
except ImportError:
7+
8+
def add_event_listener(el, event_type, func):
9+
el.addEventListener(event_type, func)
10+
11+
412
from pyscript.magic_js import document
513

614

@@ -27,19 +35,32 @@ def decorator(func):
2735
f"Invalid selector: {selector}. Selector must"
2836
" be a string, a pydom.Element or a pydom.ElementCollection."
2937
)
38+
try:
39+
sig = inspect.signature(func)
40+
# Function doesn't receive events
41+
if not sig.parameters:
3042

31-
sig = inspect.signature(func)
32-
# Function doesn't receive events
33-
if not sig.parameters:
43+
def wrapper(*args, **kwargs):
44+
func()
3445

46+
else:
47+
wrapper = func
48+
49+
except AttributeError:
50+
# TODO: this is currently an quick hack to get micropython working but we need
51+
# to actually properly replace inspect.signature with something else
3552
def wrapper(*args, **kwargs):
36-
func()
53+
try:
54+
return func(*args, **kwargs)
55+
except TypeError as e:
56+
if "takes 0 positional arguments" in str(e):
57+
return func()
58+
59+
raise
60+
61+
for el in elements:
62+
add_event_listener(el, event_type, wrapper)
3763

38-
for el in elements:
39-
add_event_listener(el, event_type, wrapper)
40-
else:
41-
for el in elements:
42-
add_event_listener(el, event_type, func)
4364
return func
4465

4566
return decorator
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .pydom import dom as pydom

pyscript.core/src/stdlib/pyweb/pydom.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
1-
import sys
2-
import warnings
3-
from functools import cached_property
4-
from typing import Any
1+
try:
2+
from typing import Any
3+
except ImportError:
4+
Any = "Any"
5+
6+
try:
7+
import warnings
8+
except ImportError:
9+
# TODO: For now it probably means we are in MicroPython. We should figure
10+
# out the "right" way to handle this. For now we just ignore the warning
11+
# and logging to console
12+
class warnings:
13+
@staticmethod
14+
def warn(*args, **kwargs):
15+
print("WARNING: ", *args, **kwargs)
16+
17+
18+
try:
19+
from functools import cached_property
20+
except ImportError:
21+
# TODO: same comment about micropython as above
22+
cached_property = property
23+
24+
try:
25+
from pyodide.ffi import JsProxy
26+
except ImportError:
27+
# TODO: same comment about micropython as above
28+
def JsProxy(obj):
29+
return obj
30+
531

6-
from pyodide.ffi import JsProxy
732
from pyscript import display, document, window
833

934
alert = window.ale 10000 rt
@@ -361,7 +386,7 @@ def __getitem__(self, key):
361386
return self.options[key]
362387

363388

364-
class StyleProxy(dict):
389+
class StyleProxy: # (dict):
365390
def __init__(self, element: Element) -> None:
366391
self._element = element
367392

@@ -480,7 +505,7 @@ def __repr__(self):
480505

481506

482507
class DomScope:
483-
def __getattr__(self, __name: str) -> Any:
508+
def __getattr__(self, __name: str):
484509
element = document[f"#{__name}"]
485510
if element:
486511
return element[0]
@@ -494,7 +519,12 @@ class PyDom(BaseElement):
494519
ElementCollection = ElementCollection
495520

496521
def __init__(self):
497-
super().__init__(document)
522+
# PyDom is a special case of BaseElement where we don't want to create a new JS element
523+
# and it really doesn't have a need for styleproxy or parent to to call to __init__
524+
# (which actually fails in MP for some reason)
525+
self._js = document
526+
self._parent = None
527+
self._proxies = {}
498528
self.ids = DomScope()
499529
self.body = Element(document.body)
500530
self.head = Element(document.head)
@@ -503,16 +533,10 @@ def create(self, type_, classes=None, html=None):
503533
return super().create(type_, is_child=False, classes=classes, html=html)
504534

505535
def __getitem__(self, key):
506-
if isinstance(key, int):
507-
indices = range(*key.indices(len(self.list)))
508-
return [self.list[i] for i in indices]
509-
510536
elements = self._js.querySelectorAll(key)
511537
if not elements:
512538
return None
513539
return ElementCollection([Element(el) for el in elements])
514540

515541

516542
dom = PyDom()
517-
518-
sys.modules[__name__] = dom

pyscript.core/test/pydom.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>PyScript Next Plugin</title>
6+
<title>PyDom Example</title>
77
<link rel="stylesheet" href="../dist/core.css">
88
<script type="module" src="../dist/core.js"></script>
99
</head>

pyscript.core/test/pydom.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
import random
2+
import time
23
from datetime import datetime as dt
34

4-
from pyscript import display
5+
from pyscript import display, when
56
from pyweb import pydom
6-
from pyweb.base import when
77

88

99
@when("click", "#just-a-button")
10-
def on_click(event):
11-
print(f"Hello from Python! {dt.now()}")
12-
display(f"Hello from Python! {dt.now()}", append=False, target="result")
10+
def on_click():
11+
try:
12+
timenow = dt.now()
13+
except NotImplementedError:
14+
# In this case we assume it's not implemented because we are using MycroPython
15+
tnow = time.localtime()
16+
tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}"
17+
timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:])
18+
19+
display(f"Hello from PyScript, time is: {timenow}", append=False, target="result")
1320

1421

1522
@when("click", "#color-button")
1623
def on_color_click(event):
17-
print("1")
1824
btn = pydom["#result"]
19-
print("2")
2025
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
2126

2227

23-
def reset_color():
28+
@when("click", "#color-reset-button")
29+
def reset_color(*args, **kwargs):
2430
pydom["#result"].style["background-color"] = "white"
2531

2632

pyscript.core/test/pydom_mp.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>PyDom Example (MicroPython)</title>
7+
<link rel="stylesheet" href="../dist/core.css">
8+
<script type="module" src="../dist/core.js"></script>
9+
</head>
10+
<body>
11+
<script type="mpy" src="pydom.py"></script>
12+
13+
<button id="just-a-button">Click For Time</button>
14+
<button id="color-button">Click For Color</button>
15+
<button id="color-reset-button">Reset Color</button>
16+
17+
<div id="result"></div>
18+
</body>
19+
</html>

pyscript.core/test/pyscript_dom/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<html lang="en">
22
<head>
3-
<title>PyperCard PyTest Suite</title>
3+
<title>PyDom Test Suite</title>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width,initial-scale=1">
66
<link rel="stylesheet" href="../../dist/core.css">
@@ -32,7 +32,7 @@
3232
</style>
3333
</head>
3434
<body>
35-
<script type="py" src="run_tests.py" config="tests.toml"></script>
35+
<script type="py" src="./run_tests.py" config="./tests.toml"></script>
3636

3737
<h1>pyscript.dom Tests</h1>
3838
<p>You can pass test parameters to this test suite by passing them as query params on the url.

pyscript.core/test/pyscript_dom/tests/test_dom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def test_select_element_add(self):
336336
assert select.options[0].html == "Option 1"
337337

338338
# WHEN we add another option (blank this time)
339-
select.options.add()
339+
select.options.add("")
340340

341341
# EXPECT the select element to have 2 options
342342
assert len(select.options) == 2

pyscript.core/tests/integration/support.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
ROOT = py.path.local(__file__).dirpath("..", "..", "..")
1919
BUILD = ROOT.join("pyscript.core").join("dist")
20+
TEST = ROOT.join("pyscript.core").join("test")
2021

2122

2223
def params_with_marks(params):
@@ -206,6 +207,14 @@ def init(self, request, tmpdir, logger, page, execution_thread):
206207
self.tmpdir = tmpdir
207208
# create a symlink to BUILD inside tmpdir
208209
tmpdir.join("build").mksymlinkto(BUILD)
210+
# create a symlink ALSO to dist folder so we can run the tests in
211+
# the test folder
212+
tmpdir.join("dist").mksymlinkto(BUILD)
213+
# create a symlink to TEST inside tmpdir so we can run tests in that
214+
# manual test folder
215+
tmpdir.join("test").mksymlinkto(TEST)
216+
217+
# create a symlink to the favicon, so that we can use it in the HTML
209218
self.tmpdir.chdir()
210219
self.tmpdir.join("favicon.ico").write("")
211220
self.logger = logger

0 commit comments

Comments
 (0)
0