Types API¶
StateBackend¶
pydantic_ai_backends.backends.state.StateBackend
¶
A filesystem kept as a JSON document: files, and the directories created.
What :class:~pydantic_ai_backends.workspaces.StateWorkspace serves as a
Pydantic AI workspace, and what a host persists between runs — files and
directories are both plain JSON, so they fit a PostgreSQL jsonb column.
The operations follow a filesystem's rules and raise its errors, because the
workspace contract asks for exactly that: a missing path is
FileNotFoundError, reading a directory IsADirectoryError, a file standing
where a directory is expected NotADirectoryError.
Paths are absolute POSIX paths; . and .. are collapsed as text, since
there are no symlinks here for .. to climb out of. directories records
every directory created, by make_dir or as the parent of a written file, so
one stays when the last thing in it is removed, as it would on disk. A
directory something is stored under exists too, which is how a document
written before directories existed keeps its tree.
Text is stored as lines and anything that is not UTF-8 as base64, so the
document is always JSON and read_bytes returns exactly what was written.
Example
import json
from pydantic_ai_backends import StateBackend
state = StateBackend()
state.write_bytes("/src/app.py", b"print('hello')")
state.make_dir("/data")
saved = json.dumps({"files": state.files, "directories": sorted(state.directories)})
loaded = json.loads(saved)
restored = StateBackend(files=loaded["files"], directories=loaded["directories"])
Source code in src/pydantic_ai_backends/backends/state.py
| Python | |
|---|---|
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
files
property
¶
Files by absolute path; JSON-serialisable as it stands.
directories
property
¶
Directories created; persist it with sorted() beside files.
__init__(files=None, directories=None)
¶
Load a document, or start an empty one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
dict[str, FileData] | None
|
Files by path. A document a previous instance produced loads
unchanged, including one written before |
None
|
directories
|
Iterable[str] | None
|
Directories created, by path. Any directory holding a file exists anyway, so a document from an earlier version, which lists none, loads unchanged. |
None
|
Source code in src/pydantic_ai_backends/backends/state.py
is_file(path)
¶
is_dir(path)
¶
Whether path is the root, a directory created, or holds anything.
Source code in src/pydantic_ai_backends/backends/state.py
exists(path)
¶
read_bytes(path)
¶
A file's exact bytes.
Raises:
| Type | Description |
|---|---|
IsADirectoryError
|
|
NotADirectoryError
|
A file stands where one of its parents should be. |
FileNotFoundError
|
Nothing is at |
ValueError
|
The stored content is base64 that does not decode — a document this class did not write. |
Source code in src/pydantic_ai_backends/backends/state.py
write_bytes(path, data)
¶
Store a file, creating and recording its parents.
Raises:
| Type | Description |
|---|---|
IsADirectoryError
|
|
NotADirectoryError
|
A file stands where one of its parents should be. |
Source code in src/pydantic_ai_backends/backends/state.py
size(path)
¶
list_dir(path)
¶
The names directly in a directory, each with whether it is one, sorted.
Raises:
| Type | Description |
|---|---|
NotADirectoryError
|
|
FileNotFoundError
|
Nothing is at |
Source code in src/pydantic_ai_backends/backends/state.py
make_dir(path)
¶
Create a directory and any missing parents; one that exists is fine.
Raises:
| Type | Description |
|---|---|
FileExistsError
|
A file is at |
NotADirectoryError
|
A file stands where one of its parents should be. |
Source code in src/pydantic_ai_backends/backends/state.py
remove(path)
¶
Remove a file, or a directory and everything under it.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
Nothing is at |
Source code in src/pydantic_ai_backends/backends/state.py
FileInfo¶
pydantic_ai_backends.types.FileInfo
¶
Bases: _FileInfoFields
Information about a file or directory.
modified_at is an ISO 8601 timestamp, present when the backend can report
one: StateBackend records it on every write, filesystem-backed listings
take st_mtime, and the remote wire carries it end to end. A backend that
derives its listing from shell ls output has no reliable timestamp to
give, so the key is absent there — read it with .get("modified_at") and
treat a missing key as unknown, never as "just now".
Source code in src/pydantic_ai_backends/types.py
from pydantic_ai_backends import FileInfo
# Example
file_info: FileInfo = {
"name": "app.py",
"path": "/workspace/app.py",
"is_dir": False,
"size": 1234,
"modified_at": "2026-08-16T12:00:00+00:00", # absent when the workspace cannot report one
}
FileData¶
pydantic_ai_backends.types.FileData
¶
Bases: _FileDataFields
One file as StateBackend stores it.
A dictionary of these is a JSON document, and callers depend on that:
the backend's whole use beyond a single process is that a host can persist
StateBackend.files and hand it back to StateBackend(files=...) later.
That guarantee is what encoding exists for.
Text is stored as content, split on newlines, with no encoding key.
Content that is not valid UTF-8 — a PNG, a zip — is stored base64 in a
single content entry with encoding="base64", and only the backend's own
readers ever see the difference.
It used to be lines of text unconditionally, with bytes decoded using
errors="surrogateescape". That round-tripped correctly in Python, which
is exactly why it survived: the lone surrogates it produces are re-encoded
by read_bytes into the original bytes, and json.dumps emits them without
complaint while json.loads reads them back. Nothing stricter accepts them.
PostgreSQL jsonb rejects an unpaired escape outright, a text column
cannot hold one because it is not valid UTF-8, and any non-Python reader of
the same document refuses it. So a workspace was serialisable right up until
an agent wrote an image into it, and the failure landed at the storage layer
rather than at the write that caused it.
A document written before encoding existed still loads: no key means text,
and the surrogates such a document may contain are encoded back to their
original bytes as they always were.
Source code in src/pydantic_ai_backends/types.py
encoding
instance-attribute
¶
Present only when content holds base64 rather than lines of text.
from pydantic_ai_backends import FileData
# Text: lines, and no encoding key.
file_data: FileData = {
"content": ["line 1", "line 2", "line 3"],
"created_at": "2024-01-15T10:30:00Z",
"modified_at": "2024-01-15T11:00:00Z",
}
# Content that is not valid UTF-8: one base64 entry, marked.
image_data: FileData = {
"content": ["iVBORw0KGgo..."],
"created_at": "2024-01-15T10:30:00Z",
"modified_at": "2024-01-15T11:00:00Z",
"encoding": "base64",
}
A dictionary of these is always a JSON document, which is what lets a host
persist a StateBackend and restore it.
WriteResult¶
pydantic_ai_backends.types.WriteResult
dataclass
¶
from pydantic_ai_backends import WriteResult
# Success
result = WriteResult(path="/workspace/app.py")
# Error
result = WriteResult(error="Permission denied")
EditResult¶
pydantic_ai_backends.types.EditResult
dataclass
¶
from pydantic_ai_backends import EditResult
# Success
result = EditResult(path="/workspace/app.py", occurrences=3)
# Error
result = EditResult(error="String not found")
ExecuteResponse¶
pydantic_ai_backends.types.ExecuteResponse
dataclass
¶
from pydantic_ai_backends import ExecuteResponse
# Example
response = ExecuteResponse(
output="Hello, World!\n",
exit_code=0,
truncated=False,
)
GrepMatch¶
pydantic_ai_backends.types.GrepMatch
¶
from pydantic_ai_backends import GrepMatch
# Example
match: GrepMatch = {
"path": "/workspace/app.py",
"line_number": 42,
"line": "def hello_world():",
}
RuntimeConfig¶
pydantic_ai_backends.types.RuntimeConfig
¶
Bases: BaseModel
Configuration for a Docker runtime environment.
Describes a pre-configured execution environment so a DockerSandbox needs
no manual package installation. Give it either a ready-made image, or a
base_image plus packages to build from.
Example
from pydantic_ai_backends import RuntimeConfig, DockerSandbox
# Custom runtime with ML packages
ml_runtime = RuntimeConfig(
name="ml-env",
description="Machine learning environment",
base_image="python:3.12-slim",
packages=["torch", "transformers", "datasets"],
)
sandbox = DockerSandbox(runtime=ml_runtime)
Source code in src/pydantic_ai_backends/types.py
| Python | |
|---|---|
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
name
instance-attribute
¶
Unique name for the runtime (e.g., "python-datascience").
description = ''
class-attribute
instance-attribute
¶
Human-readable description of the runtime.
image = None
class-attribute
instance-attribute
¶
Ready-to-use Docker image (e.g., "myregistry/python-ds:v1").
base_image = None
class-attribute
instance-attribute
¶
Base image to build upon (e.g., "python:3.12-slim").
packages = []
class-attribute
instance-attribute
¶
Packages to install (e.g., ["pandas", "numpy", "matplotlib"]).
package_manager = 'pip'
class-attribute
instance-attribute
¶
Package manager to use for installation.
setup_commands = []
class-attribute
instance-attribute
¶
Additional setup commands to run (e.g., ["apt-get update"]).
env_vars = {}
class-attribute
instance-attribute
¶
Environment variables to set in the container.
work_dir = '/workspace'
class-attribute
instance-attribute
¶
Working directory inside the container.
cache_image = True
class-attribute
instance-attribute
¶
Whether to cache the built image locally.
run_as_uid = None
class-attribute
instance-attribute
¶
Build the image around an unprivileged user, and run containers as them.
None — the default — runs as root, which is what a container does unless
told otherwise. Naming a uid instead adds three things to the generated
image: a real user with that id (so whoami and anything else calling
getpwuid works — the base image's own, if it has one at that id), a home
directory it owns, and, if the image has python3, a virtualenv at
:data:VENV_PATH it owns, put first on PATH.
The virtualenv is the part that makes this workable rather than merely
safer. A non-root user cannot write to the interpreter's own
site-packages, so without one an agent's first pip install fails — and
uv, unlike pip, has no --user mode to fall back on, so it fails with no
way forward at all. Owning a virtualenv means both simply work, and console
scripts land somewhere already on PATH.
Only meaningful with base_image: a ready-made image was not built with
this user, so running it unprivileged would leave an agent unable to install
anything system-wide.
The uid has to match whoever owns the workspace mounted into the container, which is why it is a number rather than a name.