@pyscript/core 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/core.js +3 -3
- package/dist/core.js.map +1 -1
- package/dist/error-87e0706c.js +2 -0
- package/dist/error-87e0706c.js.map +1 -0
- package/docs/README.md +3 -12
- package/package.json +4 -3
- package/src/config.js +110 -0
- package/src/core.js +84 -106
- package/src/fetch.js +3 -0
- package/src/plugins/error.js +1 -1
- package/src/plugins.js +1 -1
- package/src/stdlib/pyscript/__init__.py +34 -0
- package/src/stdlib/pyscript/display.py +154 -0
- package/src/stdlib/pyscript/event_handling.py +45 -0
- package/src/stdlib/pyscript/magic_js.py +32 -0
- package/src/stdlib/pyscript/util.py +22 -0
- package/src/stdlib/pyscript.js +9 -5
- package/src/stdlib/pyweb/pydom.py +314 -0
- package/tests/integration/__init__.py +0 -0
- package/tests/integration/conftest.py +184 -0
- package/tests/integration/support.py +1038 -0
- package/tests/integration/test_00_support.py +495 -0
- package/tests/integration/test_01_basic.py +353 -0
- package/tests/integration/test_02_display.py +452 -0
- package/tests/integration/test_03_element.py +303 -0
- package/tests/integration/test_assets/line_plot.png +0 -0
- package/tests/integration/test_assets/tripcolor.png +0 -0
- package/tests/integration/test_async.py +197 -0
- package/tests/integration/test_event_handling.py +193 -0
- package/tests/integration/test_importmap.py +66 -0
- package/tests/integration/test_interpreter.py +98 -0
- package/tests/integration/test_plugins.py +419 -0
- package/tests/integration/test_py_config.py +294 -0
- package/tests/integration/test_py_repl.py +663 -0
- package/tests/integration/test_py_terminal.py +270 -0
- package/tests/integration/test_runtime_attributes.py +64 -0
- package/tests/integration/test_script_type.py +121 -0
- package/tests/integration/test_shadow_root.py +33 -0
- package/tests/integration/test_splashscreen.py +124 -0
- package/tests/integration/test_stdio_handling.py +370 -0
- package/tests/integration/test_style.py +47 -0
- package/tests/integration/test_warnings_and_banners.py +32 -0
- package/tests/integration/test_zz_examples.py +419 -0
- package/tests/integration/test_zzz_docs_snippets.py +305 -0
- package/types/config.d.ts +3 -0
- package/types/core.d.ts +1 -2
- package/types/fetch.d.ts +1 -0
- package/types/plugins/error.d.ts +1 -1
- package/types/stdlib/pyscript.d.ts +8 -4
- package/dist/error-91f1c2f6.js +0 -2
- package/dist/error-91f1c2f6.js.map +0 -1
@@ -0,0 +1,305 @@
|
|
1
|
+
import re
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
from .support import PyScriptTest, skip_worker
|
5
|
+
|
6
|
+
|
7
|
+
@pytest.mark.skip(
|
8
|
+
reason="SKIPPING Docs: these should be reviewed ALL TOGETHER as we fix docs"
|
9
|
+
)
|
10
|
+
class TestDocsSnippets(PyScriptTest):
|
11
|
+
@skip_worker("FIXME: js.document")
|
12
|
+
def test_tutorials_py_click(self):
|
13
|
+
self.pyscript_run(
|
14
|
+
"""
|
15
|
+
<button
|
16
|
+
py-click="current_time()"
|
17
|
+
id="get-time" class="py-button">
|
18
|
+
Get current time
|
19
|
+
</button>
|
20
|
+
<p id="current-time"></p>
|
21
|
+
|
22
|
+
<py-script>
|
23
|
+
from pyscript import Element
|
24
|
+
import datetime
|
25
|
+
|
26
|
+
def current_time():
|
27
|
+
now = datetime.datetime.now()
|
28
|
+
|
29
|
+
# Get paragraph element by id
|
30
|
+
paragraph = Element("current-time")
|
31
|
+
|
32
|
+
# Add current time to the paragraph element
|
33
|
+
paragraph.write(now.strftime("%Y-%m-%d %H:%M:%S"))
|
34
|
+
</py-script>
|
35
|
+
"""
|
36
|
+
)
|
37
|
+
|
38
|
+
btn = self.page.wait_for_selector("#get-time")
|
39
|
+
btn.click()
|
40
|
+
|
41
|
+
current_time = self.page.wait_for_selector("#current-time")
|
42
|
+
|
43
|
+
pattern = "\\d+-\\d+-\\d+\\s\\d+:\\d+:\\d+" # e.g. 08-09-2022 15:57:32
|
44
|
+
assert re.search(pattern, current_time.inner_text())
|
45
|
+
self.assert_no_banners()
|
46
|
+
|
47
|
+
def test_tutorials_requests(self):
|
48
|
+
self.pyscript_run(
|
49
|
+
"""
|
50
|
+
<py-config>
|
51
|
+
packages = ["requests", "pyodide-http"]
|
52
|
+
</py-config>
|
53
|
+
|
54
|
+
<py-script>
|
55
|
+
import requests
|
56
|
+
import pyodide_http
|
57
|
+
|
58
|
+
# Patch the Requests library so it works with Pyscript
|
59
|
+
pyodide_http.patch_all()
|
60
|
+
|
61
|
+
# Make a request to the JSON Placeholder API
|
62
|
+
response = requests.get("https://jsonplaceholder.typicode.com/todos")
|
63
|
+
print(response.json())
|
64
|
+
</py-script>
|
65
|
+
"""
|
66
|
+
)
|
67
|
+
|
68
|
+
py_terminal = self.page.wait_for_selector("py-terminal")
|
69
|
+
# Just a small check to confirm that the response was received
|
70
|
+
assert "userId" in py_terminal.inner_text()
|
71
|
+
self.assert_no_banners()
|
72
|
+
|
73
|
+
@skip_worker("FIXME: js.document")
|
74
|
+
def test_tutorials_py_config_fetch(self):
|
75
|
+
# flake8: noqa
|
76
|
+
self.pyscript_run(
|
77
|
+
"""
|
78
|
+
<py-config>
|
79
|
+
[[fetch]]
|
80
|
+
from = "https://pyscript.net/examples/"
|
81
|
+
files = ["utils.py"]
|
82
|
+
[[fetch]]
|
83
|
+
from = "https://gist.githubusercontent.com/FabioRosado/faba0b7f6ad4438b07c9ac567c73b864/raw/37603b76dc7ef7997bf36781ea0116150f727f44/"
|
84
|
+
files = ["todo.py"]
|
85
|
+
</py-config>
|
86
|
+
<py-script>
|
87
|
+
from todo import add_task, add_task_event
|
88
|
+
</py-script>
|
89
|
+
<section>
|
90
|
+
<div class="text-center w-full mb-8">
|
91
|
+
<h1 class="text-3xl font-bold text-gray-800 uppercase tracking-tight">
|
92
|
+
To Do List
|
93
|
+
</h1>
|
94
|
+
</div>
|
95
|
+
<div>
|
96
|
+
<input id="new-task-content" class="py-input" type="text">
|
97
|
+
<button id="new-task-btn" class="py-button" type="submit" py-click="add_task()">
|
98
|
+
Add task
|
99
|
+
</button>
|
100
|
+
</div>
|
101
|
+
<div id="list-tasks-container" class="flex flex-col-reverse mt-4"></div>
|
102
|
+
<template id="task-template">
|
103
|
+
<section class="task py-li-element">
|
104
|
+
<label for="flex items-center p-2 ">
|
105
|
+
<input class="mr-2" type="checkbox">
|
106
|
+
<p class="m-0 inline"></p>
|
107
|
+
</label>
|
108
|
+
</section>
|
109
|
+
</template
|
110
|
+
"""
|
111
|
+
)
|
112
|
+
|
113
|
+
todo_input = self.page.locator("input")
|
114
|
+
submit_task_button = self.page.locator("button")
|
115
|
+
|
116
|
+
todo_input.type("Fold laundry")
|
117
|
+
submit_task_button.click()
|
118
|
+
|
119
|
+
first_task = self.page.locator("#task-0")
|
120
|
+
assert "Fold laundry" in first_task.inner_text()
|
121
|
+
|
122
|
+
task_checkbox = first_task.locator("input")
|
123
|
+
# Confirm that the new task isn't checked
|
124
|
+
assert not task_checkbox.is_checked()
|
125
|
+
|
126
|
+
# Let's mark it as done now
|
127
|
+
task_checkbox.check()
|
128
|
+
|
129
|
+
# Basic check that the task has the line-through class
|
130
|
+
assert (
|
131
|
+
'<p class="m-0 inline line-through">Fold laundry</p>'
|
132
|
+
in first_task.inner_html()
|
133
|
+
)
|
134
|
+
self.assert_no_banners()
|
135
|
+
|
136
|
+
def test_tutorials_py_config_interpreter(self):
|
137
|
+
"""Load a previous version of Pyodide"""
|
138
|
+
self.pyscript_run(
|
139
|
+
"""
|
140
|
+
<py-config>
|
141
|
+
[[interpreters]]
|
142
|
+
src = "https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"
|
143
|
+
name = "pyodide-0.23.0"
|
144
|
+
lang = "python"
|
145
|
+
</py-config>
|
146
|
+
<py-script>
|
147
|
+
import pyodide
|
148
|
+
print(pyodide.__version__)
|
149
|
+
</py-script>
|
150
|
+
"""
|
151
|
+
)
|
152
|
+
|
153
|
+
py_terminal = self.page.wait_for_selector("py-terminal")
|
154
|
+
assert "0.23.0" in py_terminal.inner_text()
|
155
|
+
self.assert_no_banners()
|
156
|
+
|
157
|
+
@skip_worker("FIXME: display()")
|
158
|
+
def test_tutorials_writing_to_page(self):
|
159
|
+
self.pyscript_run(
|
160
|
+
"""
|
161
|
+
<div id="manual-write"></div>
|
162
|
+
<button py-click="write_to_page()" id="manual">Say Hello</button>
|
163
|
+
<div id="display-write"></div>
|
164
|
+
<button py-click="display_to_div()" id="display">Say Things!</button>
|
165
|
+
<div>
|
166
|
+
<py-terminal>
|
167
|
+
</div>
|
168
|
+
<button py-click="print_to_page()" id="print">Print Things!</button>
|
169
|
+
|
170
|
+
<py-script>
|
171
|
+
def write_to_page():
|
172
|
+
manual_div = Element("manual-write")
|
173
|
+
manual_div.element.innerHTML = "<p><b>Hello World</b></p>"
|
174
|
+
|
175
|
+
def display_to_div():
|
176
|
+
display("I display things!", target="display-write")
|
177
|
+
|
178
|
+
def print_to_page():
|
179
|
+
print("I print things!")
|
180
|
+
</py-script>
|
181
|
+
"""
|
182
|
+
)
|
183
|
+
btn_manual = self.page.wait_for_selector("#manual")
|
184
|
+
btn_display = self.page.wait_for_selector("#display")
|
185
|
+
btn_print = self.page.wait_for_selector("#print")
|
186
|
+
|
187
|
+
btn_manual.click()
|
188
|
+
manual_write_div = self.page.wait_for_selector("#manual-write")
|
189
|
+
assert "<p><b>Hello World</b></p>" in manual_write_div.inner_html()
|
190
|
+
|
191
|
+
btn_display.click()
|
192
|
+
display_write_div = self.page.wait_for_selector("#display-write")
|
193
|
+
assert "I display things!" in display_write_div.inner_text()
|
194
|
+
|
195
|
+
btn_print.click()
|
196
|
+
py_terminal = self.page.wait_for_selector("py-terminal")
|
197
|
+
assert "I print things!" in py_terminal.inner_text()
|
198
|
+
self.assert_no_banners()
|
199
|
+
|
200
|
+
def test_guides_asyncio(self):
|
201
|
+
self.pyscript_run(
|
202
|
+
"""
|
203
|
+
<py-script>
|
204
|
+
import asyncio
|
205
|
+
|
206
|
+
async def main():
|
207
|
+
for i in range(3):
|
208
|
+
print(i)
|
209
|
+
|
210
|
+
asyncio.ensure_future(main())
|
211
|
+
</py-script>
|
212
|
+
"""
|
213
|
+
)
|
214
|
+
py_terminal = self.page.wait_for_selector("py-terminal")
|
215
|
+
|
216
|
+
assert "0\n1\n2\n" in py_terminal.inner_text()
|
217
|
+
|
218
|
+
@skip_worker("FIXME: js.document")
|
219
|
+
def test_reference_pyterminal_xterm(self):
|
220
|
+
self.pyscript_run(
|
221
|
+
"""
|
222
|
+
<py-config>
|
223
|
+
xterm = true
|
224
|
+
</py-config>
|
225
|
+
<py-script>
|
226
|
+
print("HELLO!")
|
227
|
+
import js
|
228
|
+
import asyncio
|
229
|
+
|
230
|
+
async def adjust_term_size(columns, rows):
|
231
|
+
xterm = await js.document.querySelector('py-terminal').xtermReady
|
232
|
+
xterm.resize(columns, rows)
|
233
|
+
print("test-done")
|
234
|
+
|
235
|
+
asyncio.ensure_future(adjust_term_size(40, 10))
|
236
|
+
</py-script>
|
237
|
+
"""
|
238
|
+
)
|
239
|
+
self.page.get_by_text("test-done").wait_for()
|
240
|
+
|
241
|
+
py_terminal = self.page.locator("py-terminal")
|
242
|
+
print(dir(py_terminal))
|
243
|
+
print(type(py_terminal))
|
244
|
+
assert py_terminal.evaluate("el => el.xterm.cols") == 40
|
245
|
+
assert py_terminal.evaluate("el => el.xterm.rows") == 10
|
246
|
+
|
247
|
+
@skip_worker(reason="FIXME: js.document (@when decorator)")
|
248
|
+
def test_reference_when_simple(self):
|
249
|
+
self.pyscript_run(
|
250
|
+
"""
|
251
|
+
<button id="my_btn">Click Me to Say Hi</button>
|
252
|
+
<py-script>
|
253
|
+
from pyscript import when
|
254
|
+
@when("click", selector="#my_btn")
|
255
|
+
def say_hello():
|
256
|
+
print(f"Hello, world!")
|
257
|
+
</py-script>
|
258
|
+
"""
|
259
|
+
)
|
260
|
+
self.page.get_by_text("Click Me to Say Hi").click()
|
261
|
+
self.wait_for_console("Hello, world!")
|
262
|
+
assert ("Hello, world!") in self.console.log.lines
|
263
|
+
|
264
|
+
@skip_worker(reason="FIXME: js.document (@when decorator)")
|
265
|
+
def test_reference_when_complex(self):
|
266
|
+
self.pyscript_run(
|
267
|
+
"""
|
268
|
+
<div id="container">
|
269
|
+
<button>First</button>
|
270
|
+
<button>Second</button>
|
271
|
+
<button>Third</button>
|
272
|
+
</div>
|
273
|
+
<py-script>
|
274
|
+
from pyscript import when
|
275
|
+
import js
|
276
|
+
|
277
|
+
@when("click", selector="#container button")
|
278
|
+
def highlight(evt):
|
279
|
+
#Set the clicked button's background to green
|
280
|
+
evt.target.style.backgroundColor = 'green'
|
281
|
+
|
282
|
+
#Set the background of all buttons to red
|
283
|
+
other_buttons = (button for button in js.document.querySelectorAll('button') if button != evt.target)
|
284
|
+
for button in other_buttons:
|
285
|
+
button.style.backgroundColor = 'red'
|
286
|
+
|
287
|
+
print("set") # Test Only
|
288
|
+
</py-script>
|
289
|
+
"""
|
290
|
+
)
|
291
|
+
|
292
|
+
def getBackgroundColor(locator):
|
293
|
+
return locator.evaluate(
|
294
|
+
"(element) => getComputedStyle(element).getPropertyValue('background-color')"
|
295
|
+
)
|
296
|
+
|
297
|
+
first_button = self.page.get_by_text("First")
|
298
|
+
assert getBackgroundColor(first_button) == "rgb(239, 239, 239)"
|
299
|
+
|
300
|
+
first_button.click()
|
301
|
+
self.wait_for_console("set")
|
302
|
+
|
303
|
+
assert getBackgroundColor(first_button) == "rgb(0, 128, 0)"
|
304
|
+
assert getBackgroundColor(self.page.get_by_text("Second")) == "rgb(255, 0, 0)"
|
305
|
+
assert getBackgroundColor(self.page.get_by_text("Third")) == "rgb(255, 0, 0)"
|
package/types/core.d.ts
CHANGED
package/types/fetch.d.ts
CHANGED
package/types/plugins/error.d.ts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export
|
1
|
+
export function notify(message: any): void;
|
@@ -1,9 +1,13 @@
|
|
1
|
-
declare
|
2
|
-
|
1
|
+
declare namespace _default {
|
2
|
+
let pyscript: {
|
3
3
|
"__init__.py": string;
|
4
4
|
"display.py": string;
|
5
5
|
"event_handling.py": string;
|
6
|
+
"magic_js.py": string;
|
7
|
+
"util.py": string;
|
6
8
|
};
|
7
|
-
|
8
|
-
|
9
|
+
let pyweb: {
|
10
|
+
"pydom.py": string;
|
11
|
+
};
|
12
|
+
}
|
9
13
|
export default _default;
|
package/dist/error-91f1c2f6.js
DELETED
@@ -1,2 +0,0 @@
|
|
1
|
-
import{hooks as e}from"./core.js";function n(e){const n=document.createElement("div");n.className="py-error",n.textContent=e,n.style.cssText="\n border: 1px solid red;\n background: #ffdddd;\n color: black;\n font-family: courier, monospace;\n white-space: pre;\n overflow-x: auto;\n padding: 8px;\n margin-top: 8px;\n ",document.body.append(n)}e.onBeforeRun.add((function o(r){e.onBeforeRun.delete(o);const{stderr:t}=r.io;r.io.stderr=(e,...o)=>(n(e.message||e),t(e,...o)),addEventListener("error",(({message:e})=>{e.startsWith("Uncaught PythonError")&&n(e)}))}));
|
2
|
-
//# sourceMappingURL=error-91f1c2f6.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"error-91f1c2f6.js","sources":["../src/plugins/error.js"],"sourcesContent":["// PyScript Error Plugin\nimport { hooks } from \"../core.js\";\n\nhooks.onBeforeRun.add(function override(pyScript) {\n // be sure this override happens only once\n hooks.onBeforeRun.delete(override);\n\n // trap generic `stderr` to propagate to it regardless\n const { stderr } = pyScript.io;\n\n // override it with our own logic\n pyScript.io.stderr = (error, ...rest) => {\n notify(error.message || error);\n // let other plugins or stderr hook, if any, do the rest\n return stderr(error, ...rest);\n };\n\n // be sure uncaught Python errors are also visible\n addEventListener(\"error\", ({ message }) => {\n if (message.startsWith(\"Uncaught PythonError\")) notify(message);\n });\n});\n\n// Error hook utilities\n\n// Custom function to show notifications\nfunction notify(message) {\n const div = document.createElement(\"div\");\n div.className = \"py-error\";\n div.textContent = message;\n div.style.cssText = `\n border: 1px solid red;\n background: #ffdddd;\n color: black;\n font-family: courier, monospace;\n white-space: pre;\n overflow-x: auto;\n padding: 8px;\n margin-top: 8px;\n `;\n document.body.append(div);\n}\n"],"names":["notify","message","div","document","createElement","className","textContent","style","cssText","body","append","hooks","onBeforeRun","add","override","pyScript","delete","stderr","io","error","rest","addEventListener","startsWith"],"mappings":"kCA0BA,SAASA,EAAOC,GACZ,MAAMC,EAAMC,SAASC,cAAc,OACnCF,EAAIG,UAAY,WAChBH,EAAII,YAAcL,EAClBC,EAAIK,MAAMC,QAAU,6MAUpBL,SAASM,KAAKC,OAAOR,EACzB,CAtCAS,EAAMC,YAAYC,KAAI,SAASC,EAASC,GAEpCJ,EAAMC,YAAYI,OAAOF,GAGzB,MAAMG,OAAEA,GAAWF,EAASG,GAG5BH,EAASG,GAAGD,OAAS,CAACE,KAAUC,KAC5BpB,EAAOmB,EAAMlB,SAAWkB,GAEjBF,EAAOE,KAAUC,IAI5BC,iBAAiB,SAAS,EAAGpB,cACrBA,EAAQqB,WAAW,yBAAyBtB,EAAOC,EAAQ,GAEvE"}
|