sandfs 0.1.0__tar.gz

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.
sandfs-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 George Lydakis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sandfs-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: sandfs
3
+ Version: 0.1.0
4
+ Summary: Composable virtual filesystem sandbox with a UNIX-like command surface for agents
5
+ Project-URL: Homepage, https://example.com/sandfs
6
+ Project-URL: Repository, https://example.com/sandfs.git
7
+ Author: sandfs contributors
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,in-memory,sandbox,virtual filesystem
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Topic :: System :: Filesystems
16
+ Requires-Python: >=3.10
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # sandfs
22
+
23
+ `sandfs` is an experimental Python package that implements an entirely virtual filesystem (VFS) that can be embedded inside AI agent tooling. Each sandbox keeps its own private directory tree, supports dynamic nodes that are hydrated on demand, and exposes a small UNIX-like command surface (`cd`, `ls`, `cat`, `grep`, `rg`, etc.) so agents can reuse familiar workflows.
24
+
25
+ > **Status**: design prototype. APIs are subject to change.
26
+
27
+ ## Why?
28
+
29
+ Agent builders frequently need a scratch filesystem for planning, iterating on generated code, and testing hypotheses. Shipping a simple `exec` tool that proxies every bash command to the host disk is unsafe. `sandfs` keeps those operations inside a controlled, in-memory sandbox while still feeling like a mini Linux environment.
30
+
31
+ ## Features
32
+
33
+ - In-memory directories and files that never touch the host disk unless exported.
34
+ - Dynamic nodes backed by callables (e.g., query a DB, fetch from an API, or generate text on the fly).
35
+ - Pure-Python shell/executor that understands a handful of GNU-style commands and can be extended with your own.
36
+ - Optional Python execution helper that evaluates snippets against the sandbox state only.
37
+ - Bridge to real GNU utilities via `host -p <path> <command>` which materializes the subtree and runs the host binary against it.
38
+ - Node policies (read-only, append-only, visibility labels) plus shell views so different agents see only the nodes they are allowed to.
39
+ - Write hooks and optimistic versions so hosts can flush files to external stores with conflict detection.
40
+ - Storage adapters and snapshots so you can mount external state and roll forward/back within a turn.
41
+ - Serialization helpers to snapshot or hydrate sandboxes (planned).
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install git+https://github.com/lydakis/sandfs.git
47
+ # Once a PyPI release is available: pip install sandfs
48
+ ```
49
+
50
+ ## Quickstart
51
+
52
+ ```python
53
+ from sandfs import VirtualFileSystem, SandboxShell
54
+
55
+ vfs = VirtualFileSystem()
56
+ vfs.write_file("/notes/todo.txt", "- build VFS\n- add rg support\n")
57
+
58
+ shell = SandboxShell(vfs)
59
+ print(shell.exec("ls /notes").stdout)
60
+ print(shell.exec("rg 'VFS' /notes").stdout)
61
+ ```
62
+
63
+ ### Agent shell usage
64
+
65
+ ```python
66
+ from sandfs import ProvidedNode, SandboxShell, VirtualFileSystem
67
+
68
+ vfs = VirtualFileSystem()
69
+ vfs.write_file("/workspace/app.py", "print('hello')\n")
70
+
71
+ def logs_provider(ctx):
72
+ return {"latest.log": ProvidedNode.file(content="generated on demand")}
73
+
74
+ vfs.mount_directory("/workspace/logs", logs_provider)
75
+
76
+ shell = SandboxShell(vfs)
77
+ shell.exec("cd /workspace")
78
+ print(shell.exec("ls").stdout)
79
+ print(shell.exec("tree").stdout)
80
+ ```
81
+
82
+ ### Running host GNU tools
83
+
84
+ `SandboxShell` exposes `host` to materialize a subtree onto the host disk and invoke any installed command:
85
+
86
+ ```
87
+ host -p /workspace grep -n TODO app.py
88
+ ```
89
+
90
+ The example above exports `/workspace` into a temporary directory, runs the system `grep` inside it, then discards the files.
91
+
92
+ ### Policies & views
93
+
94
+ ```python
95
+ vfs.set_policy(
96
+ "/blue/identity/persona.md",
97
+ NodePolicy(writable=False, classification="private", principals={"alice", "bob"}),
98
+ )
99
+ shell = SandboxShell(vfs, view=VisibilityView(classifications={"public"}, principals={"alice"}))
100
+ ```
101
+
102
+ ### Agent shell mode
103
+
104
+ ```python
105
+ shell = SandboxShell(
106
+ vfs,
107
+ allowed_commands={"ls", "cat", "rg"},
108
+ max_output_bytes=8_192,
109
+ )
110
+ ```
111
+
112
+ Commands outside `allowed_commands` now return an error immediately, and outputs larger than `max_output_bytes` are rejected so partner-facing agents cannot dump oversized payloads.
113
+
114
+ ### Storage adapters
115
+
116
+ ```python
117
+ from sandfs import MemoryStorageAdapter, VirtualFileSystem
118
+
119
+ adapter = MemoryStorageAdapter(initial={"a.txt": "seed"})
120
+ vfs = VirtualFileSystem()
121
+ vfs.mount_storage("/data", adapter)
122
+
123
+ print(vfs.read_file("/data/a.txt"))
124
+ vfs.write_file("/data/a.txt", "update")
125
+ vfs.sync_storage("/data") # refresh from adapter if it changed externally
126
+ ```
127
+
128
+ `mount_storage` keeps the virtual tree synchronized with the adapter, while `sync_storage` refreshes the VFS from the latest adapter contents.
129
+
130
+ ### Snapshots
131
+
132
+ ```python
133
+ snap = vfs.snapshot()
134
+ # run risky operations ...
135
+ vfs.restore(snap)
136
+ ```
137
+
138
+ Snapshots capture the entire tree (including storage-backed nodes) so you can checkpoint before an agent action and roll back on failure.
139
+
140
+ ## Repository layout
141
+
142
+ ```
143
+ .
144
+ ├── docs/ # design/vision notes
145
+ ├── sandfs/ # library sources
146
+ └── tests/ # runtime smoke tests
147
+ ```
148
+
149
+ ## Local development
150
+
151
+ ```
152
+ python -m venv .venv
153
+ source .venv/bin/activate
154
+ uv pip install -e .[dev]
155
+ uv run pytest
156
+ ```
157
+
158
+ ### Persistence hooks
159
+
160
+ Register a write hook to flush files into your own store and use optimistic versions to avoid clobbering concurrent updates:
161
+
162
+ ```python
163
+ from sandfs import VirtualFileSystem
164
+ from sandfs.hooks import WriteEvent
165
+
166
+ vfs = VirtualFileSystem()
167
+
168
+ def flush(event: WriteEvent) -> None:
169
+ save_to_db(event.path, event.content, event.version)
170
+
171
+ vfs.register_write_hook("/blue/work", flush)
172
+ vfs.write_file("/blue/work/note.md", "draft")
173
+ vfs.write_file("/blue/work/note.md", "final", expected_version=1)
174
+ ```
175
+
176
+ ### Integration hooks
177
+
178
+ ```python
179
+ from sandfs.integrations import InboxRecorder
180
+
181
+ recorder = InboxRecorder()
182
+ recorder.attach(vfs, "/blue/inbox")
183
+ ```
184
+
185
+ `PathEvent` (used under the hood) carries the path, event type (`create`, `update`, `delete`), and latest content so you can fan writes into staging queues or audit logs.
186
+
187
+ ## License
188
+
189
+ MIT
190
+
191
+ ## Further reading
192
+
193
+ - [Using sandfs with Blue](docs/blue.md)
194
+ - [Publishing sandfs](docs/publishing.md)
sandfs-0.1.0/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # sandfs
2
+
3
+ `sandfs` is an experimental Python package that implements an entirely virtual filesystem (VFS) that can be embedded inside AI agent tooling. Each sandbox keeps its own private directory tree, supports dynamic nodes that are hydrated on demand, and exposes a small UNIX-like command surface (`cd`, `ls`, `cat`, `grep`, `rg`, etc.) so agents can reuse familiar workflows.
4
+
5
+ > **Status**: design prototype. APIs are subject to change.
6
+
7
+ ## Why?
8
+
9
+ Agent builders frequently need a scratch filesystem for planning, iterating on generated code, and testing hypotheses. Shipping a simple `exec` tool that proxies every bash command to the host disk is unsafe. `sandfs` keeps those operations inside a controlled, in-memory sandbox while still feeling like a mini Linux environment.
10
+
11
+ ## Features
12
+
13
+ - In-memory directories and files that never touch the host disk unless exported.
14
+ - Dynamic nodes backed by callables (e.g., query a DB, fetch from an API, or generate text on the fly).
15
+ - Pure-Python shell/executor that understands a handful of GNU-style commands and can be extended with your own.
16
+ - Optional Python execution helper that evaluates snippets against the sandbox state only.
17
+ - Bridge to real GNU utilities via `host -p <path> <command>` which materializes the subtree and runs the host binary against it.
18
+ - Node policies (read-only, append-only, visibility labels) plus shell views so different agents see only the nodes they are allowed to.
19
+ - Write hooks and optimistic versions so hosts can flush files to external stores with conflict detection.
20
+ - Storage adapters and snapshots so you can mount external state and roll forward/back within a turn.
21
+ - Serialization helpers to snapshot or hydrate sandboxes (planned).
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install git+https://github.com/lydakis/sandfs.git
27
+ # Once a PyPI release is available: pip install sandfs
28
+ ```
29
+
30
+ ## Quickstart
31
+
32
+ ```python
33
+ from sandfs import VirtualFileSystem, SandboxShell
34
+
35
+ vfs = VirtualFileSystem()
36
+ vfs.write_file("/notes/todo.txt", "- build VFS\n- add rg support\n")
37
+
38
+ shell = SandboxShell(vfs)
39
+ print(shell.exec("ls /notes").stdout)
40
+ print(shell.exec("rg 'VFS' /notes").stdout)
41
+ ```
42
+
43
+ ### Agent shell usage
44
+
45
+ ```python
46
+ from sandfs import ProvidedNode, SandboxShell, VirtualFileSystem
47
+
48
+ vfs = VirtualFileSystem()
49
+ vfs.write_file("/workspace/app.py", "print('hello')\n")
50
+
51
+ def logs_provider(ctx):
52
+ return {"latest.log": ProvidedNode.file(content="generated on demand")}
53
+
54
+ vfs.mount_directory("/workspace/logs", logs_provider)
55
+
56
+ shell = SandboxShell(vfs)
57
+ shell.exec("cd /workspace")
58
+ print(shell.exec("ls").stdout)
59
+ print(shell.exec("tree").stdout)
60
+ ```
61
+
62
+ ### Running host GNU tools
63
+
64
+ `SandboxShell` exposes `host` to materialize a subtree onto the host disk and invoke any installed command:
65
+
66
+ ```
67
+ host -p /workspace grep -n TODO app.py
68
+ ```
69
+
70
+ The example above exports `/workspace` into a temporary directory, runs the system `grep` inside it, then discards the files.
71
+
72
+ ### Policies & views
73
+
74
+ ```python
75
+ vfs.set_policy(
76
+ "/blue/identity/persona.md",
77
+ NodePolicy(writable=False, classification="private", principals={"alice", "bob"}),
78
+ )
79
+ shell = SandboxShell(vfs, view=VisibilityView(classifications={"public"}, principals={"alice"}))
80
+ ```
81
+
82
+ ### Agent shell mode
83
+
84
+ ```python
85
+ shell = SandboxShell(
86
+ vfs,
87
+ allowed_commands={"ls", "cat", "rg"},
88
+ max_output_bytes=8_192,
89
+ )
90
+ ```
91
+
92
+ Commands outside `allowed_commands` now return an error immediately, and outputs larger than `max_output_bytes` are rejected so partner-facing agents cannot dump oversized payloads.
93
+
94
+ ### Storage adapters
95
+
96
+ ```python
97
+ from sandfs import MemoryStorageAdapter, VirtualFileSystem
98
+
99
+ adapter = MemoryStorageAdapter(initial={"a.txt": "seed"})
100
+ vfs = VirtualFileSystem()
101
+ vfs.mount_storage("/data", adapter)
102
+
103
+ print(vfs.read_file("/data/a.txt"))
104
+ vfs.write_file("/data/a.txt", "update")
105
+ vfs.sync_storage("/data") # refresh from adapter if it changed externally
106
+ ```
107
+
108
+ `mount_storage` keeps the virtual tree synchronized with the adapter, while `sync_storage` refreshes the VFS from the latest adapter contents.
109
+
110
+ ### Snapshots
111
+
112
+ ```python
113
+ snap = vfs.snapshot()
114
+ # run risky operations ...
115
+ vfs.restore(snap)
116
+ ```
117
+
118
+ Snapshots capture the entire tree (including storage-backed nodes) so you can checkpoint before an agent action and roll back on failure.
119
+
120
+ ## Repository layout
121
+
122
+ ```
123
+ .
124
+ ├── docs/ # design/vision notes
125
+ ├── sandfs/ # library sources
126
+ └── tests/ # runtime smoke tests
127
+ ```
128
+
129
+ ## Local development
130
+
131
+ ```
132
+ python -m venv .venv
133
+ source .venv/bin/activate
134
+ uv pip install -e .[dev]
135
+ uv run pytest
136
+ ```
137
+
138
+ ### Persistence hooks
139
+
140
+ Register a write hook to flush files into your own store and use optimistic versions to avoid clobbering concurrent updates:
141
+
142
+ ```python
143
+ from sandfs import VirtualFileSystem
144
+ from sandfs.hooks import WriteEvent
145
+
146
+ vfs = VirtualFileSystem()
147
+
148
+ def flush(event: WriteEvent) -> None:
149
+ save_to_db(event.path, event.content, event.version)
150
+
151
+ vfs.register_write_hook("/blue/work", flush)
152
+ vfs.write_file("/blue/work/note.md", "draft")
153
+ vfs.write_file("/blue/work/note.md", "final", expected_version=1)
154
+ ```
155
+
156
+ ### Integration hooks
157
+
158
+ ```python
159
+ from sandfs.integrations import InboxRecorder
160
+
161
+ recorder = InboxRecorder()
162
+ recorder.attach(vfs, "/blue/inbox")
163
+ ```
164
+
165
+ `PathEvent` (used under the hood) carries the path, event type (`create`, `update`, `delete`), and latest content so you can fan writes into staging queues or audit logs.
166
+
167
+ ## License
168
+
169
+ MIT
170
+
171
+ ## Further reading
172
+
173
+ - [Using sandfs with Blue](docs/blue.md)
174
+ - [Publishing sandfs](docs/publishing.md)
@@ -0,0 +1,57 @@
1
+ # Using sandfs with Blue
2
+
3
+ This document shows how a Blue runtime can mount `/blue`, enforce view-specific policies, attach inbox/log recorders, and wrap each turn in snapshots.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install git+https://github.com/lydakis/sandfs.git
9
+ # once published: pip install sandfs
10
+ ```
11
+
12
+ ## Bootstrap a sandbox per chat
13
+
14
+ ```python
15
+ from sandfs import MemoryStorageAdapter, NodePolicy, SandboxShell, VirtualFileSystem, VisibilityView
16
+ from sandfs.integrations import InboxRecorder
17
+
18
+ # 1. create the sandbox
19
+ vfs = VirtualFileSystem()
20
+
21
+ # 2. mount identity + workspaces with policies
22
+ vfs.mount_storage("/blue/identity", MemoryStorageAdapter(initial=identity_blob))
23
+ vfs.set_policy(
24
+ "/blue/identity/persona.md",
25
+ NodePolicy(writable=False, classification="private", principals={chat.owner_id}),
26
+ )
27
+
28
+ # 3. attach inbox/log recorders
29
+ inbox = InboxRecorder()
30
+ inbox.attach(vfs, "/blue/inbox")
31
+
32
+ # 4. configure the shell for the chat participants
33
+ view = VisibilityView(
34
+ classifications={"public", "partner"},
35
+ principals=set(chat.participant_ids),
36
+ )
37
+ shell = SandboxShell(
38
+ vfs,
39
+ view=view,
40
+ allowed_commands={"ls", "cat", "rg", "python"},
41
+ max_output_bytes=8_000,
42
+ )
43
+ ```
44
+
45
+ ## Per-turn workflow
46
+
47
+ ```python
48
+ snapshot = vfs.snapshot()
49
+ try:
50
+ result = shell.exec(agent_command)
51
+ process_inbox(inbox.events) # fan into Blue staging queues
52
+ finally:
53
+ vfs.restore(snapshot)
54
+ inbox.events.clear()
55
+ ```
56
+
57
+ This keeps `/blue` isolated per turn while still letting the runtime flush writes into its storage layer.
@@ -0,0 +1,32 @@
1
+ # Publishing sandfs
2
+
3
+ 1. Ensure `pyproject.toml` has the correct `version` and changelog/README are up to date.
4
+ 2. Build the package:
5
+
6
+ ```bash
7
+ uv build
8
+ ```
9
+
10
+ This produces distributions under `dist/`.
11
+
12
+ 3. Publish to PyPI:
13
+
14
+ ```bash
15
+ uv publish --username <pypi-user>
16
+ ```
17
+
18
+ `uv publish` will prompt for credentials or can read `PYPI_TOKEN`. Remove `--dry-run`; uv currently publishes directly.
19
+
20
+ If you prefer Twine:
21
+
22
+ ```bash
23
+ python -m build
24
+ python -m twine upload dist/*
25
+ ```
26
+
27
+ 4. Tag the release:
28
+
29
+ ```bash
30
+ git tag -a v0.1.0 -m "sandfs v0.1.0"
31
+ git push origin v0.1.0
32
+ ```
@@ -0,0 +1,29 @@
1
+ # sandfs Vision
2
+
3
+ `sandfs` aims to provide an extensible, self-contained virtual filesystem (VFS) that can be embedded in agentic workflows. The VFS is designed to be backed by in-memory state, dynamically generated nodes, or live data sources that are exposed via provider callables. Agent developers can expose a single `exec` tool that proxies commands into the sandbox, enabling AIs to manipulate a private filesystem with familiar UNIX idioms.
4
+
5
+ ## Core Principles
6
+
7
+ 1. **Virtual-first** – The filesystem lives in memory and never touches the host disk unless explicitly exported.
8
+ 2. **Dynamic data** – Nodes can be backed by callables (or adapters around DB queries, APIs, etc.) that materialize file contents or directory listings on demand.
9
+ 3. **Command friendly** – The sandbox ships with a tiny command runner that understands a subset of GNU-style commands (`cd`, `ls`, `cat`, `grep`, `rg`, `tree`, etc.). New commands can be registered.
10
+ 4. **Execution context isolation** – Python or shell snippets can be executed against the VFS without escaping it. All side-effects remain local to the sandbox state object.
11
+ 5. **Composable layers** – Multiple providers can be mounted at different paths, enabling hybrid topologies (e.g., static project template + live DB dumps + scratch space).
12
+
13
+ ## Proposed Architecture
14
+
15
+ - `sandfs.nodes` keeps lightweight dataclasses for directories, files, and symbolic links. Nodes expose a uniform `read()` / `write()` / `iterdir()` API and support lazy population.
16
+ - `sandfs.providers` defines the provider protocol (callables receiving a `NodeContext` and returning bytes/str) plus adapters for common scenarios (e.g., `StaticText`, `CallableProvider`).
17
+ - `sandfs.vfs.VirtualFileSystem` manages the tree, the current working directory, path resolution, mounting/unmounting providers, and basic file operations.
18
+ - `sandfs.shell.SandboxShell` hosts a registry of command handlers, parses user commands via `shlex`, and executes them against the VFS, returning a `CommandResult` object.
19
+ - `sandfs.pyexec.PythonExecutor` offers a helper to run Python code with a curated globals dict that includes the VFS helpers but forbids direct disk I/O by default.
20
+
21
+ ## Roadmap Snapshot
22
+
23
+ 1. Build the foundational VFS and directory/file nodes with provider support.
24
+ 2. Layer on top a shell/executor that mimics common filesystem commands.
25
+ 3. Add serialization/import/export helpers so sandboxes can be persisted or hydrated from templates.
26
+ 4. Expand command coverage (pipelines, redirection, e.g., `head`, `tail`, `sed`) and integrate with long-lived agent sessions.
27
+ 5. Offer host integration points, such as bridging selected directories to the real FS through adapters guarded by policies.
28
+
29
+ This document captures the initial architectural intent and can be revised as the package evolves.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.20"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sandfs"
7
+ version = "0.1.0"
8
+ description = "Composable virtual filesystem sandbox with a UNIX-like command surface for agents"
9
+ requires-python = ">=3.10"
10
+ authors = [
11
+ { name = "sandfs contributors" }
12
+ ]
13
+ readme = "README.md"
14
+ license = { text = "MIT" }
15
+ keywords = ["virtual filesystem", "agents", "sandbox", "in-memory"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Topic :: System :: Filesystems",
21
+ "Topic :: Software Development :: Libraries"
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "pytest>=7.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://example.com/sandfs"
32
+ Repository = "https://example.com/sandfs.git"
33
+
34
+ [tool.hatch.metadata]
35
+ allow-direct-references = true
@@ -0,0 +1,30 @@
1
+ """sandfs package: virtual filesystem sandbox with a mini shell."""
2
+
3
+ from .vfs import VirtualFileSystem
4
+ from .shell import SandboxShell, CommandResult
5
+ from .pyexec import PythonExecutor, PythonExecutionResult
6
+ from .providers import ContentProvider, DirectoryProvider, NodeContext, ProvidedNode
7
+ from .policies import NodePolicy, VisibilityView
8
+ from .hooks import WriteEvent, WriteHook
9
+ from .integrations import PathEvent, PathHook
10
+ from .adapters import MemoryStorageAdapter, StorageAdapter
11
+
12
+ __all__ = [
13
+ "VirtualFileSystem",
14
+ "SandboxShell",
15
+ "CommandResult",
16
+ "PythonExecutor",
17
+ "PythonExecutionResult",
18
+ "ContentProvider",
19
+ "DirectoryProvider",
20
+ "NodeContext",
21
+ "ProvidedNode",
22
+ "NodePolicy",
23
+ "VisibilityView",
24
+ "WriteEvent",
25
+ "WriteHook",
26
+ "PathEvent",
27
+ "PathHook",
28
+ "StorageAdapter",
29
+ "MemoryStorageAdapter",
30
+ ]
@@ -0,0 +1,58 @@
1
+ """Storage adapter interfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Mapping, Optional
7
+
8
+
9
+ @dataclass
10
+ class StorageEntry:
11
+ content: str
12
+ version: int = 0
13
+
14
+
15
+ class StorageAdapter:
16
+ def read(self, path: str) -> StorageEntry:
17
+ raise NotImplementedError
18
+
19
+ def write(self, path: str, content: str, *, version: int) -> StorageEntry:
20
+ raise NotImplementedError
21
+
22
+ def list(self) -> Dict[str, StorageEntry]:
23
+ raise NotImplementedError
24
+
25
+ def delete(self, path: str) -> None:
26
+ raise NotImplementedError
27
+
28
+
29
+ @dataclass
30
+ class MemoryStorageAdapter(StorageAdapter):
31
+ initial: Mapping[str, str] = field(default_factory=dict)
32
+
33
+ def __post_init__(self) -> None:
34
+ self._entries: Dict[str, StorageEntry] = {
35
+ path: StorageEntry(content=text, version=0)
36
+ for path, text in self.initial.items()
37
+ }
38
+
39
+ def read(self, path: str) -> StorageEntry:
40
+ entry = self._entries.get(path)
41
+ if entry is None:
42
+ raise FileNotFoundError(path)
43
+ return StorageEntry(content=entry.content, version=entry.version)
44
+
45
+ def write(self, path: str, content: str, *, version: int) -> StorageEntry:
46
+ entry = self._entries.get(path)
47
+ if entry and entry.version != version:
48
+ raise ValueError("version mismatch")
49
+ next_version = version + 1
50
+ entry = StorageEntry(content=content, version=next_version)
51
+ self._entries[path] = entry
52
+ return StorageEntry(content=entry.content, version=entry.version)
53
+
54
+ def list(self) -> Dict[str, StorageEntry]:
55
+ return {path: StorageEntry(content=entry.content, version=entry.version) for path, entry in self._entries.items()}
56
+
57
+ def delete(self, path: str) -> None:
58
+ self._entries.pop(path, None)
@@ -0,0 +1,32 @@
1
+ """Custom exceptions for sandfs."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class SandboxError(RuntimeError):
7
+ """Base error for the sandbox."""
8
+
9
+
10
+ class NodeNotFound(SandboxError):
11
+ """Raised when a node cannot be resolved."""
12
+
13
+
14
+ class NodeExists(SandboxError):
15
+ """Raised when attempting to create a node that already exists."""
16
+
17
+
18
+ class InvalidOperation(SandboxError):
19
+ """Raised when an operation cannot be completed."""
20
+
21
+
22
+ class ProviderError(SandboxError):
23
+ """Raised when a provider callable fails."""
24
+
25
+
26
+ __all__ = [
27
+ "SandboxError",
28
+ "NodeNotFound",
29
+ "NodeExists",
30
+ "InvalidOperation",
31
+ "ProviderError",
32
+ ]
@@ -0,0 +1,20 @@
1
+ """Hook dataclasses and type hints for persistence adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Callable
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class WriteEvent:
11
+ path: str
12
+ content: str
13
+ version: int
14
+ append: bool
15
+
16
+
17
+ WriteHook = Callable[[WriteEvent], None]
18
+
19
+
20
+ __all__ = ["WriteEvent", "WriteHook"]