Skip to content

Commit 023032e

Browse files
authored
Reorganize documentation with Diataxis (#47)
Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
1 parent 6cb1581 commit 023032e

8 files changed

Lines changed: 271 additions & 30 deletions

File tree

‎Makefile‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ lint-py: ## lint python with ruff
2525
python -m ruff format --check fsspec_python
2626

2727
lint-docs: ## lint docs with mdformat and codespell
28-
python -m mdformat --check README.md $(wildcard docs/src)
29-
python -m codespell_lib README.md $(wildcard docs/src)
28+
python -m mdformat --check README.md docs/*.md
29+
python -m codespell_lib --ignore-words-list fo README.md docs/*.md
3030

3131
fix-py: ## autoformat python code with ruff
3232
python -m ruff check --fix fsspec_python
3333
python -m ruff format fsspec_python
3434

3535
fix-docs: ## autoformat docs with mdformat and codespell
36-
python -m mdformat README.md $(wildcard docs/src)
37-
python -m codespell_lib --write README.md $(wildcard docs/src)
36+
python -m mdformat README.md docs/*.md
37+
python -m codespell_lib --ignore-words-list fo --write README.md docs/*.md
3838

3939
lint: lint-py lint-docs ## run all linters
4040
lints: lint

‎README.md‎

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,23 @@
11
# fsspec-python
22

3-
Native python integration for fsspec backends
3+
Import Python source modules from fsspec filesystems.
44

55
[![Build Status](https://github.com/1kbgz/fsspec-python/actions/workflows/build.yaml/badge.svg?branch=main&event=push)](https://github.com/1kbgz/fsspec-python/actions/workflows/build.yaml)
66
[![codecov](https://codecov.io/gh/1kbgz/fsspec-python/branch/main/graph/badge.svg)](https://codecov.io/gh/1kbgz/fsspec-python)
77
[![License](https://img.shields.io/github/license/1kbgz/fsspec-python)](https://github.com/1kbgz/fsspec-python)
88
[![PyPI](https://img.shields.io/pypi/v/fsspec-python.svg)](https://pypi.python.org/pypi/fsspec-python)
99

10-
## Overview
10+
`fsspec-python` adds a filesystem-backed finder to Python's import machinery and
11+
provides an optional hook for `builtins.open`. Importers read source files from a
12+
configured root; imported code runs in the current Python process.
1113

12-
This library wraps an [fsspec filesystem](https://github.com/fsspec/filesystem_spec) so that files and folders inside it can be imported in Python.
14+
## Documentation
1315

14-
```python
15-
from fsspec import open
16+
Start with [Import a module from memory](docs/tutorial.md), a complete local lesson
17+
that needs no remote service.
1618

17-
open(f"python::s3://my-python-bucket")
18-
19-
import my_bucket_lib # s3://my-python-bucket/my_bucket_lib.py
20-
```
21-
22-
## Usage
23-
24-
In addition to the `python::` [chained protocol](https://filesystem-spec.readthedocs.io/en/latest/features.html#url-chaining) url, this library exposes a handful of functions and monkey patches for connecting Python internal mechanisms to an `fsspec`-based filesystem.
25-
26-
- install_importer: install an
27-
- `install_importer(fs: Union[str, AbstractFileSystem], **kwargs)`: install an fsspec url/args or filesystem instance as an importer
28-
- `uninstall_importer(fs: Union[str, AbstractFileSystem] = "")`: uninstall an fsspec url or filesystem instance, or if nothing provided remove the last-installed one
29-
- `install_open_hook(fsspec: str, **fsspec_args)`: monkeypatch Python's `open` builtin to run off an `fsspec` filesystem
30-
- `uninstall_open_hook()`: remove the monkeypatched `open`
31-
32-
Coming soon:
33-
34-
- Monkey patching for `os` / `os.path` / `pathlib`
35-
36-
> [!NOTE]
37-
> This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).
19+
For application work, [install and remove importers](docs/how-to-import.md) or
20+
[redirect file access](docs/how-to-open.md). The [API reference](docs/reference.md)
21+
covers signatures, return values, lookup order, and limits. Read
22+
[How filesystem imports interact with Python](docs/explanation.md) for the design
23+
and process-wide effects.

‎docs/explanation.md‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# How filesystem imports interact with Python
2+
3+
Python asks finders in `sys.meta_path` to locate modules that are not already in
4+
`sys.modules`. An fsspec finder adds another source location without copying that
5+
location into a local package installation.
6+
7+
## A filesystem root acts as a source directory
8+
9+
The finder looks for a source file under its configured root and lets Python's
10+
source loader execute the bytes. Backend authentication and storage access remain
11+
fsspec responsibilities. The importer handles module lookup and lifetime.
12+
13+
This fits directories of standalone source modules. It does not implement the
14+
package search rules needed for package directories or namespace packages.
15+
16+
## Registration affects the process
17+
18+
New finders go before existing finders, so a newly installed root can supply a name
19+
that would otherwise resolve elsewhere. Python's module cache still takes
20+
precedence: installing another finder does not replace an already imported module.
21+
22+
Removing an importer removes tracked entries from the cache. Code that retained a
23+
reference to a module or function can still call it. Cleanup therefore removes
24+
future lookup paths; it does not undo code execution or its side effects.
25+
26+
## File access is independent of import lookup
27+
28+
The open hook redirects calls to `builtins.open`, while the finder redirects module
29+
lookup. Either can be used independently. The open hook's fallback for missing read
30+
paths lets existing local file reads continue, but writes go to the configured
31+
backend. Both mechanisms affect the whole process, which makes explicit cleanup
32+
part of their lifecycle.
33+
34+
See the [tutorial](tutorial.md) for a complete importer lifecycle and the
35+
[reference](reference.md) for lookup limits.

‎docs/how-to-import.md‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# How to install and remove importers
2+
3+
Point an importer at a directory containing `.py` modules. Install the fsspec
4+
backend required by the URL in the same environment.
5+
6+
## Use a URL
7+
8+
Pass the root URL to `install_importer`, and retain the returned filesystem for
9+
cleanup:
10+
11+
```python
12+
from fsspec_python import install_importer, uninstall_importer
13+
14+
importer = install_importer("s3://my-python-bucket/modules")
15+
try:
16+
import reporting_helpers
17+
18+
result = reporting_helpers.build_report()
19+
finally:
20+
uninstall_importer(importer)
21+
```
22+
23+
Configure backend credentials through fsspec before installing the importer.
24+
Use an explicit filesystem instance when passing backend options directly:
25+
26+
```python
27+
import fsspec
28+
29+
from fsspec_python import install_importer, uninstall_importer
30+
31+
fs = fsspec.filesystem("s3", profile="reports")
32+
importer = install_importer(fs, fo="my-python-bucket/modules")
33+
try:
34+
import reporting_helpers
35+
finally:
36+
uninstall_importer(importer)
37+
```
38+
39+
## Use a chained URL
40+
41+
Resolve `python::` with `url_to_fs`; call `exit()` when finished:
42+
43+
```python
44+
from fsspec import url_to_fs
45+
46+
import fsspec_python
47+
48+
fs, _ = url_to_fs("python::file:///srv/python-modules")
49+
try:
50+
import reporting_helpers
51+
finally:
52+
fs.exit()
53+
```
54+
55+
Use source modules from a trusted location: importing executes their code with
56+
this process's permissions. See the [reference](reference.md) for supported module
57+
layouts and cleanup behavior.

‎docs/how-to-open.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# How to redirect file access
2+
3+
Use the open hook when code calls `builtins.open` and needs a filesystem-backed
4+
root. Install the backend required by the URL first.
5+
6+
```python
7+
from fsspec_python import install_open_hook, uninstall_open_hook
8+
9+
install_open_hook("memory://open-demo")
10+
try:
11+
with open("result.txt", "w") as output:
12+
output.write("complete\n")
13+
with open("result.txt") as source:
14+
print(source.read().strip())
15+
finally:
16+
uninstall_open_hook()
17+
```
18+
19+
The output is `complete`. Replace the memory URL with your target root, and pass
20+
backend storage options as keyword arguments to `install_open_hook`.
21+
22+
Keep the hook's lifetime short and restore it in `finally`. It changes
23+
`builtins.open` for the whole process. Missing read paths fall back to the original
24+
local `open`; write, append, and exclusive-create paths use the configured backend.
25+
26+
See [API reference](reference.md) for argument handling and unsupported hooks.

‎docs/reference.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Python integration reference
2+
3+
Public names are exported from `fsspec_python` unless stated otherwise.
4+
5+
## Importers
6+
7+
### `install_importer(fs, **kwargs)`
8+
9+
Accepts a URL string or `AbstractFileSystem`. For an instance, `fo` supplies the
10+
module root. A URL already supplies its root; passing `fo` with a string raises
11+
`AssertionError`. Other input types raise `TypeError`.
12+
13+
Returns the installed `PythonFileSystem`. Repeated installation of the same normalized root reuses its importer.
14+
New finders are inserted at the front of `sys.meta_path`.
15+
16+
### `uninstall_importer(fs="")`
17+
18+
Accepts the original URL or returned filesystem. An empty argument removes the
19+
last installed importer. With no installed importers, it does nothing. An unknown
20+
root raises `ValueError` when other importers remain installed.
21+
22+
Removal deletes the finder's tracked module names from `sys.modules` and removes
23+
the finder from `sys.meta_path`. Existing references to module objects remain valid.
24+
25+
### `PythonFileSystem(target_protocol=None, target_options=None, fs=None, install=True, **kwargs)`
26+
27+
Chained filesystem with protocol `python`. Exactly one of `target_protocol` and
28+
`fs` is required. `target_options` configures a newly constructed target; `fo` sets
29+
the source root for an unchained target. `install=False` suppresses automatic
30+
importer registration. `exit()` removes the importer and calls the target's
31+
`exit()` when available.
32+
33+
### `FSSpecImportFinder` and `FSSpecImportLoader`
34+
35+
The finder checks the configured root for the final component of a module name
36+
plus a Python source suffix. Matches have `is_package=False`. Package directories,
37+
namespace packages, native extensions, and normal package-relative search are not
38+
implemented. The loader reads source bytes with the filesystem's `open`.
39+
40+
## Open hook
41+
42+
### `install_open_hook(fsspec, **fsspec_args)`
43+
44+
Resolves a root URL with fsspec and replaces `builtins.open`. Read paths present on
45+
the backend use that backend; missing read paths use the original builtin.
46+
Modes containing `w`, `a`, or `x` always use the backend.
47+
48+
Backend calls receive `mode`, `encoding`, `errors`, and `newline`. `buffering`,
49+
`closefd`, and `opener` are forwarded only on the local fallback path. Nested
50+
installations do not form a restoration stack.
51+
52+
### `uninstall_open_hook()`
53+
54+
Restores the builtin saved when `fsspec_python.open` was imported.
55+
56+
## Other modules
57+
58+
`fsspec_python.utils.normalize_fsspec(fs=None, target_protocol=None, target_options=None, fo="", **kwargs)` returns a protocol/root string. Exactly
59+
one of `fs` and `target_protocol` is required. The key does not include credentials
60+
or storage options.
61+
62+
The `path` and `pathlib` modules contain no implemented hooks.

‎docs/tutorial.md‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Import a module from memory
2+
3+
We will create a Python module in an in-memory filesystem, import it, and remove
4+
its importer.
5+
6+
## Install
7+
8+
Use Python 3.10 or newer:
9+
10+
```console
11+
python -m pip install fsspec-python
12+
```
13+
14+
## Create a module
15+
16+
Create `import_demo.py`:
17+
18+
```python
19+
import fsspec
20+
21+
from fsspec_python import install_importer, uninstall_importer
22+
23+
fs = fsspec.filesystem("memory")
24+
fs.pipe_file("/import-demo/hello_remote.py", b'def greet():\n return "Hello from memory"\n')
25+
print(fs.ls("/import-demo", detail=False))
26+
```
27+
28+
Run `python import_demo.py`. The listing contains `/import-demo/hello_remote.py`.
29+
30+
## Import and call the function
31+
32+
Append:
33+
34+
```python
35+
importer = install_importer(fs, fo="/import-demo")
36+
try:
37+
import hello_remote
38+
39+
print(hello_remote.greet())
40+
finally:
41+
uninstall_importer(importer)
42+
```
43+
44+
Run the script again. Its final line is:
45+
46+
```text
47+
Hello from memory
48+
```
49+
50+
## Check cleanup
51+
52+
Append:
53+
54+
```python
55+
import sys
56+
57+
print("hello_remote" in sys.modules)
58+
```
59+
60+
Run the script. The final line is `False`: removing the importer also removed its
61+
tracked module from Python's module cache.
62+
63+
See [How to install and remove importers](how-to-import.md) to use a filesystem URL.

‎pyproject.toml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,15 @@ section-order = [
145145
"F401",
146146
"F403",
147147
]
148+
149+
[tool.yardang]
150+
title = "fsspec-python"
151+
root = "README.md"
152+
pages = [
153+
"docs/tutorial.md",
154+
"docs/how-to-import.md",
155+
"docs/how-to-open.md",
156+
"docs/reference.md",
157+
"docs/explanation.md",
158+
]
159+
use-autoapi = false

0 commit comments

Comments
 (0)