Skip to content

Commit 896ca84

Browse files
committed
Add latest version of the pyscript namespace from which the API docs can be generated.
1 parent d7de835 commit 896ca84

14 files changed

Lines changed: 4047 additions & 0 deletions

File tree

pyscript/__init__.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
This is the main `pyscript` namespace. It provides the primary Pythonic API
3+
for users to interact with PyScript features sitting on top of the browser's
4+
own API (https://developer.mozilla.org/en-US/docs/Web/API). It includes
5+
utilities for common activities such as displaying content, handling events,
6+
fetching resources, managing local storage, and coordinating with web workers.
7+
8+
Some notes about the naming conventions and the relationship between various
9+
similar-but-different names found within this code base.
10+
11+
`import pyscript`
12+
13+
This package contains the main user-facing API offered by pyscript. All
14+
the names which are supposed be used by end users should be made
15+
available in pyscript/__init__.py (i.e., this file).
16+
17+
`import _pyscript`
18+
19+
This is an internal module implemented in JS. It is used internally by
20+
the pyscript package, **end users should not use it directly**. For its
21+
implementation, grep for `interpreter.registerJsModule("_pyscript",
22+
...)` in `core.js`.
23+
24+
`import js`
25+
26+
This is the JS `globalThis`, as exported by Pyodide and/or Micropython's
27+
foreign function interface (FFI). As such, it contains different things in
28+
the main thread or in a worker, as defined by web standards.
29+
30+
`import pyscript.context`
31+
32+
This submodule abstracts away some of the differences between the main
33+
thread and a worker. In particular, it defines `window` and `document`
34+
in such a way that these names work in both cases: in the main thread,
35+
they are the "real" objects, in a worker they are proxies which work
36+
thanks to [coincident](https://github.com/WebReflection/coincident).
37+
38+
`from pyscript import window, document`
39+
40+
These are just the `window` and `document` objects as defined by
41+
`pyscript.context`. This is the blessed way to access them from `pyscript`,
42+
as it works transparently in both the main thread and worker cases.
43+
"""
44+
45+
from polyscript import lazy_py_modules as py_import
46+
from pyscript.context import (
47+
RUNNING_IN_WORKER,
48+
PyWorker,
49+
config,
50+
current_target,
51+
document,
52+
js_import,
53+
js_modules,
54+
sync,
55+
window,
56+
)
57+
from pyscript.display import HTML, display
58+
from pyscript.fetch import fetch
59+
from pyscript.storage import Storage, storage
60+
from pyscript.websocket import WebSocket
61+
from pyscript.events import when, Event
62+
63+
if not RUNNING_IN_WORKER:
64+
from pyscript.workers import create_named_worker, workers

pyscript/context.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
Execution context management for PyScript.
3+
4+
This module handles the differences between running in the main browser thread
5+
versus running in a Web Worker, providing a consistent API regardless of the
6+
execution context.
7+
8+
Key features:
9+
- Detects whether code is running in a worker or main thread. Read this via
10+
`pyscript.context.RUNNING_IN_WORKER`.
11+
- Parses and normalizes configuration from `polyscript.config` and adds the
12+
Python interpreter type via the `type` key in `pyscript.context.config`.
13+
- Provides appropriate implementations of `window`, `document`, and `sync`.
14+
- Sets up JavaScript module import system, including a lazy `js_import`
15+
function.
16+
- Manages `PyWorker` creation.
17+
- Provides access to the current display target via
18+
`pyscript.context.display_target`.
19+
20+
Main thread context:
21+
- `window` and `document` are available directly.
22+
- `PyWorker` can be created to spawn worker threads.
23+
- `sync` is not available (raises `NotSupported`).
24+
25+
Worker context:
26+
- `window` and `document` are proxied from main thread (if SharedArrayBuffer
27+
available).
28+
- `PyWorker` is not available (raises `NotSupported`).
29+
- `sync` utilities are available for main thread communication.
30+
"""
31+
32+
import json
33+
import sys
34+
35+
import js
36+
from polyscript import config as _polyscript_config
37+
from polyscript import js_modules
38+
from pyscript.util import NotSupported
39+
40+
# Detect execution context: True if running in a worker, False if main thread.
41+
RUNNING_IN_WORKER = not hasattr(js, "document")
42+
43+
# Parse and normalize configuration from polyscript.
44+
config = json.loads(js.JSON.stringify(_polyscript_config))
45+
if isinstance(config, str):
46+
config = {}
47+
48+
# Detect and add Python interpreter type to config.
49+
if "MicroPython" in sys.version:
50+
config["type"] = "mpy"
51+
else:
52+
config["type"] = "py"
53+
54+
55+
class _JSModuleProxy:
56+
"""
57+
Proxy for JavaScript modules imported via js_modules.
58+
59+
This allows Python code to import JavaScript modules using Python's
60+
import syntax:
61+
62+
```python
63+
from pyscript.js_modules lodash import debounce
64+
```
65+
66+
The proxy lazily retrieves the actual JavaScript module when accessed.
67+
"""
68+
69+
def __init__(self, name):
70+
"""
71+
Create a proxy for the named JavaScript module.
72+
"""
73+
self.name = name
74+
75+
def __getattr__(self, field):
76+
"""
77+
Retrieve a JavaScript object/function from the proxied JavaScript
78+
module via the given `field` name.
79+
"""
80+
# Avoid Pyodide looking for non-existent special methods.
81+
if not field.startswith("_"):
82+
return getattr(getattr(js_modules, self.name), field)
83+
return None
84+
85+
86+
# Register all available JavaScript modules in Python's module system.
87+
# This enables: from pyscript.js_modules.xxx import yyy
88+
for module_name in js.Reflect.ownKeys(js_modules):
89+
sys.modules[f"pyscript.js_modules.{module_name}"] = _JSModuleProxy(module_name)
90+
sys.modules["pyscript.js_modules"] = js_modules
91+
92+
93+
# Context-specific setup: Worker vs Main Thread.
94+
if RUNNING_IN_WORKER:
95+
import polyscript
96+
97+
# PyWorker cannot be created from within a worker.
98+
PyWorker = NotSupported(
99+
"pyscript.PyWorker",
100+
"pyscript.PyWorker works only when running in the main thread",
101+
)
102+
103+
# Attempt to access main thread's window and document via SharedArrayBuffer.
104+
try:
105+
window = polyscript.xworker.window
106+
document = window.document
107+
js.document = document
108+
109+
# Create js_import function that runs imports on the main thread.
110+
js_import = window.Function(
111+
"return (...urls) => Promise.all(urls.map((url) => import(url)))"
112+
)()
113+
114+
except:
115+
# SharedArrayBuffer not available - window/document cannot be proxied.
116+
sab_error_message = (
117+
"Unable to use `window` or `document` in worker. "
118+
"This requires SharedArrayBuffer support. "
119+
"See: https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
120+
)
121+
js.console.warn(sab_error_message)
122+
window = NotSupported("pyscript.window", sab_error_message)
123+
document = NotSupported("pyscript.document", sab_error_message)
124+
js_import = None
125+
126+
# Worker-specific utilities for main thread communication.
127+
sync = polyscript.xworker.sync
128+
129+
def current_target():
130+
"""
131+
Get the current output target in worker context.
132+
"""
133+
return polyscript.target
134+
135+
else:
136+
# Main thread context setup.
137+
import _pyscript
138+
from _pyscript import PyWorker as _PyWorker, js_import
139+
from pyscript.ffi import to_js
140+
141+
def PyWorker(url, **options):
142+
"""
143+
Create a Web Worker running Python code.
144+
145+
This spawns a new worker thread that can execute Python code
146+
found at the `url`, independently of the main thread. The
147+
`**options` can be used to configure the worker.
148+
149+
```python
150+
from pyscript import PyWorker
151+
152+
# Create a worker to run background tasks.
153+
# (`type` MUST be either `micropython` or `pyodide`)
154+
worker = PyWorker("./worker.py", type="micropython")
155+
```
156+
157+
PyWorker can only be created from the main thread, not from
158+
within another worker.
159+
"""
160+
return _PyWorker(url, to_js(options))
161+
162+
# Main thread has direct access to window and document.
163+
window = js
164+
document = js.document
165+
166+
# sync is not available in main thread (only in workers).
167+
sync = NotSupported(
168+
"pyscript.sync", "pyscript.sync works only when running in a worker"
169+
)
170+
171+
def current_target():
172+
"""
173+
Get the current output target in main thread context.
174+
"""
175+
return _pyscript.target

0 commit comments

Comments
 (0)