diff --git a/2024.9.2/404.html b/2024.9.2/404.html new file mode 100644 index 0000000..afa80de --- /dev/null +++ b/2024.9.2/404.html @@ -0,0 +1,766 @@ + + + +
+ + + + + + + + + + + + + + +PyScript makes available convenience objects, functions and attributes.
+In Python this is done via the builtin pyscript
module:
In HTML this is done via py-*
and mpy-*
attributes (depending on the
+interpreter you're using):
<button id="foo" py-click="handler_defined_in_python">Click me</button>
+
These APIs will work with both Pyodide and Micropython in exactly the same way.
+Info
+Both Pyodide and MicroPython provide access to two further lower-level +APIs:
+globalThis
+ via importing the js
module: import js
(now js
is a proxy for
+ globalThis
in which all native JavaScript based browser APIs are
+ found).PyScript can run in two contexts: the main browser thread, or on a web worker. +The following three categories of API functionality explain features that are +common for both main thread and worker, main thread only, and worker only. Most +features work in both contexts in exactly the same manner, but please be aware +that some are specific to either the main thread or a worker context.
+These Python objects / functions are available in both the main thread and in +code running on a web worker:
+pyscript.config
A Python dictionary representing the configuration for the interpreter.
+from pyscript import config
+
+
+# It's just a dict.
+print(config.get("files"))
+# This will be either "mpy" or "py" depending on the current interpreter.
+print(config["type"])
+
Info
+The config
object will always include a type
attribute set to either
+mpy
or py
, to indicate which version of Python your code is currently
+running in.
Warning
+Changing the config
dictionary at runtime has no effect on the actual
+configuration.
It's just a convenience to read the configuration at run time.
+pyscript.current_target
A utility function to retrieve the unique identifier of the element used
+to display content. If the element is not a <script>
and it already has
+an id
, that id
will be returned.
<!-- current_target(): explicit-id -->
+<mpy-script id="explicit-id">
+ from pyscript import display, current_target
+ display(f"current_target(): {current_target()}")
+</mpy-script>
+
+<!-- current_target(): mpy-0 -->
+<mpy-script>
+ from pyscript import display, current_target
+ display(f"current_target(): {current_target()}")
+</mpy-script>
+
+<!-- current_target(): mpy-1 -->
+<!-- creates right after the <script>:
+ <script-py id="mpy-1">
+ <div>current_target(): mpy-1</div>
+ </script-py>
+-->
+<script type="mpy">
+ from pyscript import display, current_target
+ display(f"current_target(): {current_target()}")
+</script>
+
Note
+The return value of current_target()
always references a visible element
+on the page, not at the current <script>
that is executing the code.
To reference the <script>
element executing the code, assign it an id
:
Then use the standard document.getElementById(script_id)
function to
+return a reference to it in your code.
pyscript.display
A function used to display content. The function is intelligent enough to +introspect the object[s] it is passed and work out how to correctly display the +object[s] in the web page based on the following mime types:
+text/plain
to show the content as texttext/html
to show the content as HTMLimage/png
to show the content as <img>
image/jpeg
to show the content as <img>
image/svg+xml
to show the content as <svg>
application/json
to show the content as JSONapplication/javascript
to put the content in <script>
(discouraged)The display
function takes a list of *values
as its first argument, and has
+two optional named arguments:
target=None
- the DOM element into which the content should be placed.
+ If not specified, the target
will use the current_script()
returned id
+ and populate the related dedicated node to show the content.append=True
- a flag to indicate if the output is going to be appended to
+ the target
.There are some caveats:
+display
function automatically uses
+ the current <py-script>
or <mpy-script>
tag as the target
into which
+ the content will be displayed.<script>
tag has the target
attribute, and is not a worker,
+ the element on the page with that ID (or which matches that selector)
+ will be used to display the content instead.display
function needs an explicit
+ target="dom-id"
argument to identify where the content will be
+ displayed.append=True
is the default
+ behaviour.<!-- will produce
+ <py-script>PyScript</py-script>
+-->
+<py-script worker>
+ from pyscript import display
+ display("PyScript", append=False)
+</py-script>
+
+<!-- will produce
+ <script type="py">...</script>
+ <script-py>PyScript</script-py>
+-->
+<script type="py">
+ from pyscript import display
+ display("PyScript", append=False)
+</script>
+
+<!-- will populate <h1>PyScript</h1> -->
+<script type="py" target="my-h1">
+ from pyscript import display
+ display("PyScript", append=False)
+</script>
+<h1 id="my-h1"></h1>
+
+<!-- will populate <h2>PyScript</h2> -->
+<script type="py" worker>
+ from pyscript import display
+ display("PyScript", target="my-h2", append=False)
+</script>
+<h2 id="my-h2"></h2>
+
pyscript.document
On both main and worker threads, this object is a proxy for the web page's
+document object.
+The document
is a representation of the
+DOM
+and can be used to read or manipulate the content of the web page.
pyscript.fetch
A common task is to fetch
data from the web via HTTP requests. The
+pyscript.fetch
function provides a uniform way to achieve this in both
+Pyodide and MicroPython. It is closely modelled on the
+Fetch API found
+in browsers with some important Pythonic differences.
The simple use case is to pass in a URL and await
the response. If this
+request is in a function, that function should also be defined as async
.
from pyscript import fetch
+
+
+response = await fetch("https://example.com")
+if response.ok:
+ data = await response.text()
+else:
+ print(response.status)
+
The object returned from an await fetch
call will have attributes that
+correspond to the
+JavaScript response object.
+This is useful for getting response codes, headers and other metadata before
+processing the response's data.
Alternatively, rather than using a double await
(one to get the response, the
+other to grab the data), it's possible to chain the calls into a single
+await
like this:
from pyscript import fetch
+
+data = await fetch("https://example.com").text()
+
The following awaitable methods are available to you to access the data +returned from the server:
+arrayBuffer()
returns a Python
+ memoryview of
+ the response. This is equivalent to the
+ arrayBuffer()
method
+ in the browser based fetch
API.blob()
returns a JavaScript
+ blob
+ version of the response. This is equivalent to the
+ blob()
method
+ in the browser based fetch
API.bytearray()
returns a Python
+ bytearray
+ version of the response.json()
returns a Python datastructure representing a JSON serialised
+ payload in the response.text()
returns a Python string version of the response.The underlying browser fetch
API has
+many request options
+that you should simply pass in as keyword arguments like this:
from pyscript import fetch
+
+
+result = await fetch("https://example.com", method="POST", body="HELLO").text()
+
Danger
+You may encounter +CORS +errors (especially with reference to a missing +Access-Control-Allow-Origin header.
+This is a security feature of modern browsers where the site to which you +are making a request will not process a request from a site hosted at +another domain.
+For example, if your PyScript app is hosted under example.com
and you
+make a request to bbc.co.uk
(who don't allow requests from other domains)
+then you'll encounter this sort of CORS related error.
There is nothing PyScript can do about this problem (it's a feature, not a +bug). However, you could use a pass-through proxy service to get around +this limitation (i.e. the proxy service makes the call on your behalf).
+pyscript.ffi
The pyscript.ffi
namespace contains foreign function interface (FFI) methods
+that work in both Pyodide and MicroPython.
pyscript.ffi.create_proxy
A utility function explicitly for when a callback function is added via an
+event listener. It ensures the function still exists beyond the assignment of
+the function to an event. Should you not create_proxy
around the callback
+function, it will be immediately garbage collected after being bound to the
+event.
Warning
+There is some technical complexity to this situation, and we have attempted
+to create a mechanism where create_proxy
is never needed.
Pyodide expects the created proxy to be explicitly destroyed when it's
+not needed / used anymore. However, the underlying proxy.destroy()
method
+has not been implemented in MicroPython (yet).
To simplify this situation and automatically destroy proxies based on +JavaScript memory management (garbage collection) heuristics, we have +introduced an experimental flag:
+ +This flag ensures the proxy creation and destruction process is managed for
+you. When using this flag you should never need to explicitly call
+create_proxy
.
The technical details of how this works are +described here.
+pyscript.ffi.to_js
A utility function to convert Python references into their JavaScript
+equivalents. For example, a Python dictionary is converted into a JavaScript
+object literal (rather than a JavaScript Map
), unless a dict_converter
+is explicitly specified and the runtime is Pyodide.
The technical details of how this works are described here.
+pyscript.js_modules
It is possible to define JavaScript modules to use within your Python code.
+Such named modules will always then be available under the
+pyscript.js_modules
namespace.
Warning
+Please see the documentation (linked above) about restrictions and gotchas +when configuring how JavaScript modules are made available to PyScript.
+pyscript.storage
The pyscript.storage
API wraps the browser's built-in
+IndexDB
+persistent storage in a synchronous Pythonic API.
Info
+The storage API is persistent per user tab, page, or domain, in the same +way IndexedDB persists.
+This API is not saving files in the interpreter's virtual file system +nor onto the user's hard drive.
+from pyscript import storage
+
+
+# Each store must have a meaningful name.
+store = await storage("my-storage-name")
+
+# store is a dictionary and can now be used as such.
+
The returned dictionary automatically loads the current state of the referenced +IndexDB. All changes are automatically queued in the background.
+# This is a write operation.
+store["key"] = value
+
+# This is also a write operation (it changes the stored data).
+del store["key"]
+
Should you wish to be certain changes have been synchronized to the underlying
+IndexDB, just await store.sync()
.
Common types of value can be stored via this API: bool
, float
, int
, str
+and None
. In addition, data structures like list
, dict
and tuple
can
+be stored.
Warning
+Because of the way the underlying data structure are stored in IndexDB,
+a Python tuple
will always be returned as a Python list
.
It is even possible to store arbitrary data via a bytearray
or
+memoryview
object. However, there is a limitation that such values must be
+stored as a single key/value pair, and not as part of a nested data
+structure.
Sometimes you may need to modify the behaviour of the dict
like object
+returned by pyscript.storage
. To do this, create a new class that inherits
+from pyscript.Storage
, then pass in your class to pyscript.storage
as the
+storage_class
argument:
from pyscript import window, storage, Storage
+
+
+class MyStorage(Storage):
+
+ def __setitem__(self, key, value):
+ super().__setitem__(key, value)
+ window.console.log(key, value)
+ ...
+
+
+store = await storage("my-data-store", storage_class=MyStorage)
+
+# The store object is now an instance of MyStorage.
+
@pyscript/core/dist/storage.js
The equivalent functionality based on the JS module can be found through our module.
+The goal is to be able to share the same database across different worlds (interpreters) and the functionality is nearly identical except there is no class to provide because the storage in JS is just a dictionary proxy that synchronizes behind the scene all read, write or delete operations.
+pyscript.web
The classes and references in this namespace provide a Pythonic way to interact +with the DOM. An explanation for how to idiomatically use this API can be found +in the user guide
+pyscript.web.page
This object represents a web page. It has four attributes and two methods:
+html
- a reference to a Python object representing the document's html root element.head
- a reference to a Python object representing the document's head.body
- a reference to a Python object representing the document's body.title
- the page's title (usually displayed in the browser's title bar or a page's tab.find
- a method that takes a single selector
+ argument and returns a collection of Python objects representing the matching
+ elements.append
- a shortcut for page.body.append
(to add new elements to the
+ page).You may also shortcut the find
method by enclosing a CSS selector in square
+brackets: page["#my-thing"]
.
These are provided as a convenience so you have several simple and obvious +options for accessing and changing the content of the page.
+All the Python objects returned by these attributes and method are instances of
+classes relating to HTML elements defined in the pyscript.web
namespace.
pyscript.web.*
There are many classes in this namespace. Each is a one-to-one mapping of any +HTML element name to a Python class representing the HTML element of that +name. Each Python class ensures only valid properties and attributes can be +assigned, according to web standards.
+Usage of these classes is +explained in the user guide.
+Info
+The full list of supported element/class names is:
+grid
+a, abbr, address, area, article, aside, audio
+b, base, blockquote, body, br, button
+canvas, caption, cite, code, col, colgroup
+data, datalist, dd, del_, details, dialog, div, dl, dt
+em, embed
+fieldset, figcaption, figure, footer, form
+h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html
+i, iframe, img, input_, ins
+kbd
+label, legend, li, link
+main, map_, mark, menu, meta, meter
+nav
+object_, ol, optgroup, option, output
+p, param, picture, pre, progress
+q
+s, script, section, select, small, source, span, strong, style, sub, summary, sup
+table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track
+u, ul
+var, video
+wbr
+
These correspond to the standard
+HTML elements
+with the caveat that del_
and input_
have the trailing underscore
+(_
) because they are also keywords in Python, and the grid
is a custom
+class for a div
with a grid
style display
property.
All these classes ultimately derive from the
+pyscript.web.elements.Element
base class.
In addition to properties defined by the HTML standard for each type of HTML
+element (e.g. title
, src
or href
), all elements have the following
+properties and methods (in alphabetical order):
append(child)
- add the child
element to the element's children.children
- a collection containing the element's child elements (that it
+ contains).classes
- a set of CSS classes associated with the element.clone(clone_id=None)
- Make a clone of the element (and the underlying DOM
+ object), and assign it the optional clone_id
.find(selector)
- use a CSS selector to find matching child elements.parent
- the element's parent element (that contains it).show_me
- scroll the element into view.style
- a dictionary of CSS style properties associated with the element.update(classes=None, style=None, **kwargs)
- update the element with the
+ specified classes (set), style (dict) and DOM properties (kwargs)._dom_element
- a reference to the proxy object that represents the
+ underlying native HTML element.Info
+All elements, by virtue of inheriting from the base Element
class, may
+have the following properties:
The classes
set-like object has the following convenience functions:
add(*class_names)
- add the class(es) to the element.contains(class_name)
- indicate if class_name
is associated with the
+ element.remove(*class_names)
- remove the class(es) from the element.replace(old_class, new_class)
- replace the old_class
with new_class
.toggle(class_name)
- add a class if it is absent, or remove a class if it
+ is present.Elements that require options (such as the datalist
, optgroup
and select
+elements), can have options passed in when they are created:
Notice how options can be a tuple of two values (the name and associated value) +or just the single name (whose associated value will default to the given +name).
+It's possible to access and manipulate the options
of the resulting elements:
selected_option = my_select.options.selected
+my_select.options.remove(0) # Remove the first option (in position 0).
+my_select.clear()
+my_select.options.add(html="Orange")
+
Finally, the collection of elements returned by find
and children
is
+iterable, indexable and sliceable:
Furthermore, four attributes related to all elements contained in the +collection can be read (as a list) or set (applied to all contained elements):
+classes
- the list of classes associated with the elements.innerHTML
- the innerHTML of each element.style
- a dictionary like object for interacting with CSS style rules.value
- the value
attribute associated with each element.pyscript.when
A Python decorator to indicate the decorated function should handle the +specified events for selected elements.
+The decorator takes two parameters:
+event_type
should be the name of the
+ browser event to handle
+ as a string (e.g. "click"
).selector
should be a string containing a
+ valid selector
+ to indicate the target elements in the DOM whose events of event_type
are
+ of interest.The following example has a button with an id of my_button
and a decorated
+function that handles click
events dispatched by the button.
from pyscript import when, display
+
+
+@when("click", "#my_button")
+def click_handler(event):
+ """
+ Event handlers get an event object representing the activity that raised
+ them.
+ """
+ display("I've been clicked!")
+
This functionality is related to the py-*
or mpy-*
HTML attributes.
pyscript.window
On the main thread, this object is exactly the same as import js
which, in
+turn, is a proxy of JavaScript's
+globalThis
+object.
On a worker thread, this object is a proxy for the web page's +global window context.
+Warning
+The reference for pyscript.window
is always a reference to the main
+thread's global window context.
If you're running code in a worker this is not the worker's own global
+context. A worker's global context is always reachable via import js
+(the js
object being a proxy for the worker's globalThis
).
pyscript.HTML
A class to wrap generic content and display it as un-escaped HTML on the page.
+<script type="mpy">
+ from pyscript import display, HTML
+
+ # Escaped by default:
+ display("<em>em</em>") # <em>em</em>
+</script>
+
+<script type="mpy">
+ from pyscript import display, HTML
+
+ # Un-escaped raw content inserted into the page:
+ display(HTML("<em>em</em>")) # <em>em</em>
+</script>
+
pyscript.RUNNING_IN_WORKER
This constant flag is True
when the current code is running within a
+worker. It is False
when the code is running within the main thread.
pyscript.WebSocket
If a pyscript.fetch
results in a call and response HTTP interaction with a
+web server, the pyscript.Websocket
class provides a way to use
+websockets
+for two-way sending and receiving of data via a long term connection with a
+web server.
PyScript's implementation, available in both the main thread and a web worker, +closely follows the browser's own +WebSocket class.
+This class accepts the following named arguments:
+url
pointing at the ws or wss address. E.g.:
+ WebSocket(url="ws://localhost:5037/")
protocols
, an optional string or a list of strings as
+ described here.The WebSocket
class also provides these convenient static constants:
WebSocket.CONNECTING
(0
) - the ws.readyState
value when a web socket
+ has just been created.WebSocket.OPEN
(1
) - the ws.readyState
value once the socket is open.WebSocket.CLOSING
(2
) - the ws.readyState
after ws.close()
is
+ explicitly invoked to stop the connection.WebSocket.CLOSED
(3
) - the ws.readyState
once closed.A WebSocket
instance has only 2 methods:
ws.send(data)
- where data
is either a string or a Python buffer,
+ automatically converted into a JavaScript typed array. This sends data via
+ the socket to the connected web server.ws.close(code=0, reason="because")
- which optionally accepts code
and
+ reason
as named arguments to signal some specific status or cause for
+ closing the web socket. Otherwise ws.close()
works with the default
+ standard values.A WebSocket
instance also has the fields that the JavaScript
+WebSocket
instance will have:
send()
but not yet transmitted to the network.WebSocket
static constants (CONNECTING
, OPEN
,
+ etc...).WebSocket
+ instance.A WebSocket
instance can have the following listeners. Directly attach
+handler functions to them. Such functions will always receive a single
+event
object.
WebSocket
's connection is closed.WebSocket
. If the event.data
is a
+ JavaScript typed array instead of a string, the reference it will point
+ directly to a memoryview of the underlying bytearray
data.The following code demonstrates a pyscript.WebSocket
in action.
<script type="mpy" worker>
+ from pyscript import WebSocket
+
+ def onopen(event):
+ print(event.type)
+ ws.send("hello")
+
+ def onmessage(event):
+ print(event.type, event.data)
+ ws.close()
+
+ def onclose(event):
+ print(event.type)
+
+ ws = WebSocket(url="ws://localhost:5037/")
+ ws.onopen = onopen
+ ws.onmessage = onmessage
+ ws.onclose = onclose
+</script>
+
Info
+It's also possible to pass in any handler functions as named arguments when
+you instantiate the pyscript.WebSocket
class:
pyscript.js_import
If a JavaScript module is only needed under certain circumstances, we provide +an asynchronous way to import packages that were not originally referenced in +your configuration.
+<script type="py">
+from pyscript import js_import, window
+
+escaper, = await js_import("https://esm.run/html-escaper")
+
+window.console.log(escaper)
+
The js_import
call returns an asynchronous tuple containing the JavaScript
+modules referenced as string arguments.
pyscript.py_import
Warning
+This is an experimental feature.
+Feedback and bug reports are welcome!
+If you have a lot of Python packages referenced in your configuration, startup +performance may be degraded as these are downloaded.
+If a Python package is only needed under certain circumstances, we provide an +asynchronous way to import packages that were not originally referenced in your +configuration.
+<script type="py">
+from pyscript import py_import
+
+matplotlib, regex, = await py_import("matplotlib", "regex")
+
+print(matplotlib, regex)
+</script>
+
The py_import
call returns an asynchronous tuple containing the Python
+modules provided by the packages referenced as string arguments.
pyscript.PyWorker
A class used to instantiate a new worker from within Python.
+Note
+Sometimes we disambiguate between interpreters through naming conventions
+(e.g. py
or mpy
).
However, this class is always PyWorker
and the desired interpreter
+MUST be specified via a type
option. Valid values for the type of
+interpreter are either micropython
or pyodide
.
The following fragments demonstrate how to evaluate the file worker.py
on a
+new worker from within Python.
from pyscript import RUNNING_IN_WORKER, display, sync
+
+display("Hello World", target="output", append=True)
+
+# will log into devtools console
+print(RUNNING_IN_WORKER) # True
+print("sleeping")
+sync.sleep(1)
+print("awake")
+
from pyscript import PyWorker
+
+# type MUST be either `micropython` or `pyodide`
+PyWorker("worker.py", type="micropython")
+
<script type="mpy" src="./main.py">
+<div id="output"></div> <!-- The display target -->
+
pyscript.workers
The pyscript.workers
reference allows Python code in the main thread to
+easily access named workers (and their exported functionality).
For example, the following Pyodide code may be running on a named worker
+(see the name
attribute of the script
tag):
<script type="py" worker name="py-version">
+import sys
+
+def version():
+ return sys.version
+
+# define what to export to main consumers
+__export__ = ["version"]
+</script>
+
While over on the main thread, this fragment of MicroPython will be able to
+access the worker's version
function via the workers
reference:
<script type="mpy">
+from pyscript import workers
+
+pyworker = await workers["py-version"]
+
+# print the pyodide version
+print(await pyworker.version())
+</script>
+
Importantly, the workers
reference will NOT provide a list of
+known workers, but will only await
for a reference to a named worker
+(resolving when the worker is ready). This is because the timing of worker
+startup is not deterministic.
Should you wish to await for all workers on the page at load time, it's +possible to loop over matching elements in the document like this:
+<script type="mpy">
+from pyscript import document, workers
+
+for el in document.querySelectorAll("[type='py'][worker][name]"):
+ await workers[el.getAttribute('name')]
+
+# ... rest of the code
+</script>
+
pyscript.sync
A function used to pass serializable data from workers to the main thread.
+Imagine you have this code on the main thread:
+from pyscript import PyWorker
+
+def hello(name="world"):
+ display(f"Hello, {name}")
+
+worker = PyWorker("./worker.py")
+worker.sync.hello = hello
+
In the code on the worker, you can pass data back to handler functions like +this:
+from pyscript import sync
+
+sync.hello("PyScript")
+
As a convenience, and to ensure backwards compatibility, PyScript allows the +use of inline event handlers via custom HTML attributes.
+Warning
+This classic pattern of coding (inline event handlers) is no longer +considered good practice in web development circles.
+We include this behaviour for historic reasons, but the folks at +Mozilla have a good explanation +of why this is currently considered bad practice.
+These attributes, expressed as py-*
or mpy-*
attributes of an HTML element,
+reference the name of a Python function to run when the event is fired. You
+should replace the *
with the actual name of an event (e.g. py-click
or
+mpy-click
). This is similar to how all
+event handlers on elements
+start with on
in standard HTML (e.g. onclick
). The rule of thumb is to
+simply replace on
with py-
or mpy-
and then reference the name of a
+Python function.
<button py-click="handle_click" id="my_button">Click me!</button>
+
from pyscript import window
+
+
+def handle_click(event):
+ """
+ Simply log the click event to the browser's console.
+ """
+ window.console.log(event)
+
Under the hood, the pyscript.when
decorator is used to
+enable this behaviour.
Note
+In earlier versions of PyScript, the value associated with the attribute +was simply evaluated by the Python interpreter. This was unsafe: +manipulation of the attribute's value could have resulted in the evaluation +of arbitrary code.
+This is why we changed to the current behaviour: just supply the name +of the Python function to be evaluated, and PyScript will do this safely.
+{"use strict";/*!
+ * escape-html
+ * Copyright(c) 2012-2013 TJ Holowaychuk
+ * Copyright(c) 2015 Andreas Lubbe
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
+ * MIT Licensed
+ */var _a=/["'&<>]/;Pn.exports=Aa;function Aa(e){var t=""+e,r=_a.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i