@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,294 @@
|
|
1
|
+
import os
|
2
|
+
import tarfile
|
3
|
+
import tempfile
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
import requests
|
8
|
+
|
9
|
+
from .support import PyScriptTest, with_execution_thread
|
10
|
+
|
11
|
+
PYODIDE_VERSION = "0.23.4"
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.fixture
|
15
|
+
def pyodide_tar(request):
|
16
|
+
"""
|
17
|
+
Fixture which returns a local copy of pyodide. It uses pytest-cache to
|
18
|
+
avoid re-downloading it between runs.
|
19
|
+
"""
|
20
|
+
URL = (
|
21
|
+
f"https://github.com/pyodide/pyodide/releases/download/{PYODIDE_VERSION}/"
|
22
|
+
f"pyodide-core-{PYODIDE_VERSION}.tar.bz2"
|
23
|
+
)
|
24
|
+
tar_name = Path(URL).name
|
25
|
+
|
26
|
+
val = request.config.cache.get(tar_name, None)
|
27
|
+
if val is None:
|
28
|
+
response = requests.get(URL, stream=True)
|
29
|
+
TMP_DIR = tempfile.mkdtemp()
|
30
|
+
TMP_TAR_LOCATION = os.path.join(TMP_DIR, tar_name)
|
31
|
+
with open(TMP_TAR_LOCATION, "wb") as f:
|
32
|
+
f.write(response.raw.read())
|
33
|
+
val = TMP_TAR_LOCATION
|
34
|
+
request.config.cache.set(tar_name, val)
|
35
|
+
return val
|
36
|
+
|
37
|
+
|
38
|
+
def unzip(location, extract_to="."):
|
39
|
+
file = tarfile.open(name=location, mode="r:bz2")
|
40
|
+
file.extractall(path=extract_to)
|
41
|
+
|
42
|
+
|
43
|
+
# Disable the main/worker dual testing, for two reasons:
|
44
|
+
#
|
45
|
+
# 1. the <py-config> logic happens before we start the worker, so there is
|
46
|
+
# no point in running these tests twice
|
47
|
+
#
|
48
|
+
# 2. the logic to inject execution_thread into <py-config> works only with
|
49
|
+
# plain <py-config> tags, but here we want to test all weird combinations
|
50
|
+
# of config
|
51
|
+
@with_execution_thread(None)
|
52
|
+
class TestConfig(PyScriptTest):
|
53
|
+
def test_py_config_inline(self):
|
54
|
+
self.pyscript_run(
|
55
|
+
"""
|
56
|
+
<py-config>
|
57
|
+
name = "foobar"
|
58
|
+
</py-config>
|
59
|
+
|
60
|
+
<py-script async>
|
61
|
+
from pyscript import window, document
|
62
|
+
promise = await document.currentScript._pyodide.promise
|
63
|
+
window.console.log("config name:", promise.config.name)
|
64
|
+
</py-script>
|
65
|
+
"""
|
66
|
+
)
|
67
|
+
assert self.console.log.lines[-1] == "config name: foobar"
|
68
|
+
|
69
|
+
def test_py_config_external(self):
|
70
|
+
pyconfig_toml = """
|
71
|
+
name = "app with external config"
|
72
|
+
"""
|
73
|
+
self.writefile("pyconfig.toml", pyconfig_toml)
|
74
|
+
self.pyscript_run(
|
75
|
+
"""
|
76
|
+
<py-config src="pyconfig.toml"></py-config>
|
77
|
+
|
78
|
+
<py-script async>
|
79
|
+
from pyscript import window, document
|
80
|
+
promise = await document.currentScript._pyodide.promise
|
81
|
+
window.console.log("config name:", promise.config.name)
|
82
|
+
</py-script>
|
83
|
+
"""
|
84
|
+
)
|
85
|
+
assert self.console.log.lines[-1] == "config name: app with external config"
|
86
|
+
|
87
|
+
# The default pyodide version is newer than
|
88
|
+
# the one we are loading below (after downloading locally)
|
89
|
+
# which is 0.22.0
|
90
|
+
|
91
|
+
# The test checks if loading a different interpreter is possible
|
92
|
+
# and that too from a locally downloaded file without needing
|
93
|
+
# the use of explicit `indexURL` calculation.
|
94
|
+
def test_interpreter_config(self, pyodide_tar):
|
95
|
+
unzip(pyodide_tar, extract_to=self.tmpdir)
|
96
|
+
self.pyscript_run(
|
97
|
+
"""
|
98
|
+
<py-config type="json">
|
99
|
+
{
|
100
|
+
"interpreters": [{
|
101
|
+
"src": "/pyodide/pyodide.js",
|
102
|
+
"name": "my-own-pyodide",
|
103
|
+
"lang": "python"
|
104
|
+
}]
|
105
|
+
}
|
106
|
+
</py-config>
|
107
|
+
|
108
|
+
<py-script>
|
109
|
+
import sys, js
|
110
|
+
pyodide_version = sys.modules["pyodide"].__version__
|
111
|
+
js.console.log("version", pyodide_version)
|
112
|
+
</py-script>
|
113
|
+
""",
|
114
|
+
)
|
115
|
+
|
116
|
+
assert self.console.log.lines[-1] == f"version {PYODIDE_VERSION}"
|
117
|
+
|
118
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
119
|
+
def test_invalid_json_config(self):
|
120
|
+
# we need wait_for_pyscript=False because we bail out very soon,
|
121
|
+
# before being able to write 'PyScript page fully initialized'
|
122
|
+
self.pyscript_run(
|
123
|
+
"""
|
124
|
+
<py-config type="json">
|
125
|
+
[[
|
126
|
+
</py-config>
|
127
|
+
""",
|
128
|
+
wait_for_pyscript=False,
|
129
|
+
)
|
130
|
+
banner = self.page.wait_for_selector(".py-error")
|
131
|
+
assert "SyntaxError: Unexpected end of JSON input" in self.console.error.text
|
132
|
+
expected = (
|
133
|
+
"(PY1000): The config supplied: [[ is an invalid JSON and cannot be "
|
134
|
+
"parsed: SyntaxError: Unexpected end of JSON input"
|
135
|
+
)
|
136
|
+
assert banner.inner_text() == expected
|
137
|
+
|
138
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
139
|
+
def test_invalid_toml_config(self):
|
140
|
+
# we need wait_for_pyscript=False because we bail out very soon,
|
141
|
+
# before being able to write 'PyScript page fully initialized'
|
142
|
+
self.pyscript_run(
|
143
|
+
"""
|
144
|
+
<py-config>
|
145
|
+
[[
|
146
|
+
</py-config>
|
147
|
+
""",
|
148
|
+
wait_for_pyscript=False,
|
149
|
+
)
|
150
|
+
banner = self.page.wait_for_selector(".py-error")
|
151
|
+
assert "SyntaxError: Expected DoubleQuote" in self.console.error.text
|
152
|
+
expected = (
|
153
|
+
"(PY1000): The config supplied: [[ is an invalid TOML and cannot be parsed: "
|
154
|
+
"SyntaxError: Expected DoubleQuote, Whitespace, or [a-z], [A-Z], "
|
155
|
+
'[0-9], "-", "_" but "\\n" found.'
|
156
|
+
)
|
157
|
+
assert banner.inner_text() == expected
|
158
|
+
|
159
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
160
|
+
def test_multiple_py_config(self):
|
161
|
+
self.pyscript_run(
|
162
|
+
"""
|
163
|
+
<py-config>
|
164
|
+
name = "foobar"
|
165
|
+
</py-config>
|
166
|
+
|
167
|
+
<py-config>
|
168
|
+
this is ignored and won't even be parsed
|
169
|
+
</py-config>
|
170
|
+
|
171
|
+
<py-script>
|
172
|
+
import js
|
173
|
+
config = js.pyscript_get_config()
|
174
|
+
js.console.log("config name:", config.name)
|
175
|
+
</py-script>
|
176
|
+
"""
|
177
|
+
)
|
178
|
+
banner = self.page.wait_for_selector(".py-warning")
|
179
|
+
expected = (
|
180
|
+
"Multiple <py-config> tags detected. Only the first "
|
181
|
+
"is going to be parsed, all the others will be ignored"
|
182
|
+
)
|
183
|
+
assert banner.text_content() == expected
|
184
|
+
|
185
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
186
|
+
def test_no_interpreter(self):
|
187
|
+
snippet = """
|
188
|
+
<py-config type="json">
|
189
|
+
{
|
190
|
+
"interpreters": []
|
191
|
+
}
|
192
|
+
</py-config>
|
193
|
+
"""
|
194
|
+
self.pyscript_run(snippet, wait_for_pyscript=False)
|
195
|
+
div = self.page.wait_for_selector(".py-error")
|
196
|
+
assert (
|
197
|
+
div.text_content() == "(PY1000): Fatal error: config.interpreter is empty"
|
198
|
+
)
|
199
|
+
|
200
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
201
|
+
def test_multiple_interpreter(self):
|
202
|
+
snippet = """
|
203
|
+
<py-config type="json">
|
204
|
+
{
|
205
|
+
"interpreters": [
|
206
|
+
{
|
207
|
+
"src": "https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js",
|
208
|
+
"name": "pyodide-0.23.2",
|
209
|
+
"lang": "python"
|
210
|
+
},
|
211
|
+
{
|
212
|
+
"src": "http://...",
|
213
|
+
"name": "this will be ignored",
|
214
|
+
"lang": "this as well"
|
215
|
+
}
|
216
|
+
]
|
217
|
+
}
|
218
|
+
</py-config>
|
219
|
+
|
220
|
+
<py-script>
|
221
|
+
import js
|
222
|
+
js.console.log("hello world");
|
223
|
+
</py-script>
|
224
|
+
"""
|
225
|
+
self.pyscript_run(snippet)
|
226
|
+
banner = self.page.wait_for_selector(".py-warning")
|
227
|
+
expected = (
|
228
|
+
"Multiple interpreters are not supported yet.Only the first will be used"
|
229
|
+
)
|
230
|
+
assert banner.text_content() == expected
|
231
|
+
assert self.console.log.lines[-1] == "hello world"
|
232
|
+
|
233
|
+
def test_paths(self):
|
234
|
+
self.writefile("a.py", "x = 'hello from A'")
|
235
|
+
self.writefile("b.py", "x = 'hello from B'")
|
236
|
+
self.pyscript_run(
|
237
|
+
"""
|
238
|
+
<py-config>
|
239
|
+
[[fetch]]
|
240
|
+
files = ["./a.py", "./b.py"]
|
241
|
+
</py-config>
|
242
|
+
|
243
|
+
<py-script>
|
244
|
+
import js
|
245
|
+
import a, b
|
246
|
+
js.console.log(a.x)
|
247
|
+
js.console.log(b.x)
|
248
|
+
</py-script>
|
249
|
+
"""
|
250
|
+
)
|
251
|
+
assert self.console.log.lines[-2:] == [
|
252
|
+
"hello from A",
|
253
|
+
"hello from B",
|
254
|
+
]
|
255
|
+
|
256
|
+
@pytest.mark.skip("FIXME: We need to restore the banner.")
|
257
|
+
def test_paths_that_do_not_exist(self):
|
258
|
+
self.pyscript_run(
|
259
|
+
"""
|
260
|
+
<py-config>
|
261
|
+
[[fetch]]
|
262
|
+
files = ["./f.py"]
|
263
|
+
</py-config>
|
264
|
+
""",
|
265
|
+
wait_for_pyscript=False,
|
266
|
+
)
|
267
|
+
|
268
|
+
expected = "(PY0404): Fetching from URL ./f.py failed with " "error 404"
|
269
|
+
|
270
|
+
inner_html = self.page.locator(".py-error").inner_html()
|
271
|
+
|
272
|
+
assert expected in inner_html
|
273
|
+
assert expected in self.console.error.lines[-1]
|
274
|
+
|
275
|
+
def test_paths_from_packages(self):
|
276
|
+
self.writefile("utils/__init__.py", "")
|
277
|
+
self.writefile("utils/a.py", "x = 'hello from A'")
|
278
|
+
self.pyscript_run(
|
279
|
+
"""
|
280
|
+
<py-config>
|
281
|
+
[[fetch]]
|
282
|
+
from = "utils"
|
283
|
+
to_folder = "pkg"
|
284
|
+
files = ["__init__.py", "a.py"]
|
285
|
+
</py-config>
|
286
|
+
|
287
|
+
<py-script>
|
288
|
+
import js
|
289
|
+
from pkg.a import x
|
290
|
+
js.console.log(x)
|
291
|
+
</py-script>
|
292
|
+
"""
|
293
|
+
)
|
294
|
+
assert self.console.log.lines[-1] == "hello from A"
|