Skip to content

2025 2 3 #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,86 @@ is explicitly specified and the runtime is Pyodide.

The technical details of how this works are [described here](../user-guide/ffi#to_js).

### `pyscript.fs`

!!! danger

This API only works in Chromium based browsers.

An API for mounting the user's local filesystem to a designated directory in
the browser's virtual filesystem. Please see
[the filesystem](../user-guide/filesystem) section of the user-guide for more
information.

#### `pyscript.fs.mount`

Mount a directory on the user's local filesystem into the browser's virtual
filesystem. If no previous
[transient user activation](https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation)
has taken place, this function will result in a minimalist dialog to provide
the required transient user activation.

This asynchronous function takes four arguments:

* `path` (required) - indicating the location on the in-browser filesystem to
which the user selected directory from the local filesystem will be mounted.
* `mode` (default: `"readwrite"`) - indicates how the code may interact with
the mounted filesystem. May also be just `"read"` for read-only access.
* `id` (default: `"pyscript"`) - indicate a unique name for the handler
associated with a directory on the user's local filesystem. This allows users
to select different folders and mount them at the same path in the
virtual filesystem.
* `root` (default: `""`) - a hint to the browser for where to start picking the
path that should be mounted in Python. Valid values are: `desktop`,
`documents`, `downloads`, `music`, `pictures` or `videos` as per
[web standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#startin).

```python title="Mount a local directory to the '/local' directory in the browser's virtual filesystem"
from pyscript import fs


# May ask for permission from the user, and select the local target.
await fs.mount("/local")
```

If the call to `fs.mount` happens after a click or other transient event, the
confirmation dialog will not be shown.

```python title="Mounting without a transient event dialog."
from pyscript import fs


async def handler(event):
"""
The click event that calls this handler is already a transient event.
"""
await fs.mount("/local")


my_button.onclick = handler
```

#### `pyscript.fs.sync`

Given a named `path` for a mount point on the browser's virtual filesystem,
asynchronously ensure the virtual and local directories are synchronised (i.e.
all changes made in the browser's mounted filesystem, are propagated to the
user's local filesystem).

```python title="Synchronise the virtual and local filesystems."
await fs.sync("/local")
```

#### `pyscript.fs.unmount`

Asynchronously unmount the named `path` from the browser's virtual filesystem
after ensuring content is synchronized. This will free up memory and allow you
to re-use the path to mount a different directory.

```python title="Unmount from the virtual filesystem."
await fs.unmount("/local")
```

### `pyscript.js_modules`

It is possible to [define JavaScript modules to use within your Python code](../user-guide/configuration#javascript-modules).
Expand Down
8 changes: 4 additions & 4 deletions docs/beginning-pyscript.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ module in the document's `<head>` tag:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>🦜 Polyglot - Piratical PyScript</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.2/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.2/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.3/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.3/core.js"></script>
</head>
<body>

Expand Down Expand Up @@ -168,8 +168,8 @@ In the end, our HTML should look like this:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>🦜 Polyglot - Piratical PyScript</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.2/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.2/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.3/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.3/core.js"></script>
</head>
<body>
<h1>Polyglot 🦜 💬 🇬🇧 ➡️ 🏴‍☠️</h1>
Expand Down
8 changes: 5 additions & 3 deletions docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ version of Pyodide as specified in the previous examples:

### Files

The `files` option fetches arbitrary content from URLs onto the filesystem
available to Python, and emulated by the browser. Just map a valid URL to a
destination filesystem path.
The `files` option fetches arbitrary content from URLs onto the virtual
filesystem available to Python, and emulated by the browser. Just map a valid
URL to a destination filesystem path on the in-browser virtual filesystem. You
can find out more in the section about
[PyScript and filesystems](../filesystem/).

The following JSON and TOML are equivalent:

Expand Down
175 changes: 175 additions & 0 deletions docs/user-guide/filesystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# PyScript and Filesystems

As you know, the filesystem is where you store files. For Python to work there
needs to be a filesystem in which Python packages, modules and data for your
apps can be found. When you `import` a library, or when you `open` a file, it
is on the in-browser virtual filesystem that Python looks.

However, things are not as they may seem.

This section clarifies what PyScript means by a filesystem, and the way in
which PyScript interacts with such a concept.

## Two filesystems

PyScript interacts with two filesystems.

1. The browser, thanks to
[Emscripten](https://emscripten.org/docs/api_reference/Filesystem-API.html),
provides a virtual in-memory filesystem. **This has nothing to do with your
device's local filesystem**, but is contained within the browser based
sandbox used by PyScript. The [files](../configuration/#files)
configuration API defines what is found on this filesystem.
2. PyScript provides an easy to use API for accessing your device's local
filesystem. It requires permission from the user to mount a folder from the
local filesystem onto a directory in the browser's virtual filesystem. Think
of it as gate-keeping a bridge to the outside world of the device's local
filesystem.

!!! danger

Access to the device's local filesystem **is only available in Chromium
based browsers**.

Firefox and Safari do not support this capability (yet), and so it is not
available to PyScript running in these browsers.

## The in-browser filesystem

The filesystem that both Pyodide and MicroPython use by default is the
[in-browser virtual filesystem](https://emscripten.org/docs/api_reference/Filesystem-API.html).
Opening files and importing modules takes place in relation to this sandboxed
environment, configured via the [files](../configuration/#files) entry in your
settings.

```toml title="Filesystem configuration via TOML."
[files]
"https://example.com/myfile.txt": ""
```

```python title="Just use the resulting file 'as usual'."
# Interacting with the virtual filesystem, "as usual".
with open("myfile.txt", "r") as myfile:
print(myfile.read())
```

Currently, each time you re-load the page, the filesystem is recreated afresh,
so any data stored by PyScript to this filesystem will be lost.

!!! info

In the future, we may make it possible to configure the in-browser virtual
filesystem as persistent across re-loads.

[This article](https://emscripten.org/docs/porting/files/file_systems_overview.html)
gives an excellent overview of the browser based virtual filesystem's
implementation and architecture.

The most important key concepts to remember are:

* The PyScript filesystem is contained *within* the browser's sandbox.
* Each instance of a Python interpreter used by PyScript runs in a separate
sandbox, and so does NOT share virtual filesystems.
* All Python related filesytem operations work as expected with this
filesystem.
* The virtual filesystem is configured via the
[files](../configuration/#files) entry in your settings.
* The virtual filesystem is (currently) NOT persistent between page re-loads.
* Currently, the filesystem has a maximum capacity of 4GB of data (something
over which we have no control).

## The device's local filesystem

**Access to the device's local filesystem currently only works on Chromium
based browsers**.

Your device (the laptop, mobile or tablet) that runs your browser has a
filesystem provided by a hard drive. Thanks to the
[`pyscript.fs` namespace in our API](../../api/#pyscriptfs), both MicroPython
and Pyodide (CPython) gain access to this filesystem should the user of
your code allow this to happen.

This is a [transient activation](https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation)
for the purposes of
[user activation of gated features](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).
Put simply, before your code gains access to their local filesystem, an
explicit agreement needs to be gathered from the user. Part of this process
involves asking the user to select a target directory on their local
filesystem, to which PyScript will be given access.

The directory on their local filesystem, selected by the user, is then mounted
to a given directory inside the browser's virtual filesystem. In this way a
mapping is made between the sandboxed world of the browser, and the outside
world of the user's filesystem.

Your code will then be able to perform all the usual filesystem related
operations provided by Python, within the mounted directory. However, **such
changes will NOT take effect on the local filesystem UNTIL your code
explicitly calls the `sync` function**. At this point, the state of the
in-browser virtual filesystem and the user's local filesystem are synchronised.

The following code demonstrates the simplest use case:

```python title="The core operations of the pyscript.fs API"
from pyscript import fs

# Ask once for permission to mount any local folder
# into the virtual filesystem handled by Pyodide/MicroPython.
# The folder "/local" refers to the directory on the virtual
# filesystem to which the user-selected directory will be
# mounted.
await fs.mount("/local")

# ... DO FILE RELATED OPERATIONS HERE ...

# If changes were made, ensure these are persisted to the local filesystem's
# folder.
await fs.sync("/local")

# If needed to free RAM or that specific path, sync and unmount
await fs.unmount("/local")
```

It is possible to use multiple different local directories with the same mount
point. This is important if your application provides some generic
functionality on data that might be in different local directories because
while the nature of the data might be similar, the subject is not. For
instance, you may have different models for a PyScript based LLM in different
directories, and may wish to switch between them at runtime using different
handlers (requiring their own transient action). In which case use
the following technique:

```python title="Multiple local directories on the same mount point"
# Mount a local folder specifying a different handler.
# This requires a user explicit transient action (once).
await fs.mount("/local", id="v1")
# ... operate on that folder ...
await fs.unmount("/local")

# Mount a local folder specifying a different handler.
# This also requires a user explicit transient action (once).
await fs.mount("/local", id="v2")
# ... operate on that folder ...
await fs.unmount("/local")

# Go back to the original handler or a previous one.
# No transient action required now.
await fs.mount("/local", id="v1")
# ... operate again on that folder ...
```

In addition to the mount `path` and handler `id`, the `fs.mount` function can
take two further arguments:

* `mode` (by default `"readwrite"`) indicates the sort of activity available to
the user. It can also be set to `read` for read-only access to the local
filesystem. This is a part of the
[web-standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#mode)
for directory selection.
* `root` - (by default, `""`) is a hint to the browser for where to start
picking the path that should be mounted in Python. Valid values are:
`desktop`, `documents`, `downloads`, `music`, `pictures` or `videos`
[as per web standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#startin).

The `sync` and `unmount` functions only accept the mount `path` used in the
browser's local filesystem.
4 changes: 2 additions & 2 deletions docs/user-guide/first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ CSS:
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- PyScript CSS -->
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.2/core.css">
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.3/core.css">
<!-- This script tag bootstraps PyScript -->
<script type="module" src="https://pyscript.net/releases/2025.2.2/core.js"></script>
<script type="module" src="https://pyscript.net/releases/2025.2.3/core.js"></script>
</head>
<body>
<!-- your code goes here... -->
Expand Down
10 changes: 5 additions & 5 deletions docs/user-guide/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ For example, this will work because all references are contained within the
registered function:

```js
import { hooks } from "https://pyscript.net/releases/2025.2.2/core.js";
import { hooks } from "https://pyscript.net/releases/2025.2.3/core.js";

hooks.worker.onReady.add(() => {
// NOT suggested, just an example!
Expand All @@ -114,7 +114,7 @@ hooks.worker.onReady.add(() => {
However, due to the outer reference to the variable `i`, this will fail:

```js
import { hooks } from "https://pyscript.net/releases/2025.2.2/core.js";
import { hooks } from "https://pyscript.net/releases/2025.2.3/core.js";

// NO NO NO NO NO! ☠️
let i = 0;
Expand Down Expand Up @@ -147,7 +147,7 @@ the page.

```js title="log.js - a plugin that simply logs to the console."
// import the hooks from PyScript first...
import { hooks } from "https://pyscript.net/releases/2025.2.2/core.js";
import { hooks } from "https://pyscript.net/releases/2025.2.3/core.js";

// The `hooks.main` attribute defines plugins that run on the main thread.
hooks.main.onReady.add((wrap, element) => {
Expand Down Expand Up @@ -197,8 +197,8 @@ hooks.worker.onAfterRun.add(() => {
<!-- JS plugins should be available before PyScript bootstraps -->
<script type="module" src="./log.js"></script>
<!-- PyScript -->
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.2/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.2/core.js"></script>
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.3/core.css">
<script type="module" src="https://pyscript.net/releases/2025.2.3/core.js"></script>
</head>
<body>
<script type="mpy">
Expand Down
4 changes: 2 additions & 2 deletions docs/user-guide/workers.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ Here's how:
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- PyScript CSS -->
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.2/core.css">
<link rel="stylesheet" href="https://pyscript.net/releases/2025.2.3/core.css">
<!-- This script tag bootstraps PyScript -->
<script type="module" src="https://pyscript.net/releases/2025.2.2/core.js"></script>
<script type="module" src="https://pyscript.net/releases/2025.2.3/core.js"></script>
<title>PyWorker - mpy bootstrapping pyodide example</title>
<script type="mpy" src="main.py"></script>
</head>
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ nav:
- The DOM &amp; JavaScript: user-guide/dom.md
- Web Workers: user-guide/workers.md
- The FFI in detail: user-guide/ffi.md
- PyScript and filesystems: user-guide/filesystem.md
- Python terminal: user-guide/terminal.md
- Python editor: user-guide/editor.md
- PyGame-CE: user-guide/pygame-ce.md
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "2025.2.2"
"version": "2025.2.3"
}