Skip to content

Python SDK

The Python SDK is the canonical implementation. The CLI, MCP server, and Viewer are all thin wrappers over the same folio package.

from folio import open_sheet
sheet = open_sheet("./customers", actor="agent:demo")
print(sheet.contract.id) # "example-customers"
print(sheet.list_records(limit=5)["records"])
sheet.upsert_records([
{"id": "cust_999", "company_name": "Beta", "country": "FR"}
])
result = sheet.materialize()
# {'materialized': 1, 'skipped': 0, 'failures': [], 'total_cost': 0.0}

Module map

ImportWhat you get
folio.open_sheetThe constructor; returns a Sheet.
folio.SheetClass with the six core operations + materialize / status / provenance.
folio.load_contractStandalone contract validator.
folio.load_derivation, folio.load_derivationsRead derivation files.
folio._ai_kind.AIClientProtocol — implement to plug in your own LLM.
folio._ai_kind.AnthropicClientAdapterDefault adapter (uses anthropic).
folio._ai_kind.StubAIClientDeterministic stub for tests.
folio.kinds._http.HTTPTransportProtocol for the http derivation.
folio.kinds._http.HTTPXTransportDefault adapter (uses httpx).
folio.kinds._http.StubHTTPTransportDeterministic stub for tests.
folio.exceptions.*The exception hierarchy.

The leading-underscore modules (_ai_kind, _cache, _provenance, etc.) are private but stable for the Protocols and stubs documented here. The public API is what’s re-exported from folio.__init__.

What open_sheet does

def open_sheet(path: str | Path, *, actor: str | None = None) -> Sheet
  • Loads contract.yaml (raises ContractError on validation failure).
  • Stores the actor for write operations (writes without an actor still raise — the actor must be passed somewhere in the chain).
  • Does not read records.jsonl until you call a method that needs it.
  • Does not acquire the lock until a write happens.

A Sheet is cheap to construct (~5 ms) and reusable across many operations.

The Six core operations

SDKCLIWhat it does
sheet.get_contract()folio validate (sort of)Returns the typed contract.
sheet.query(sql, params)folio queryDuckDB SELECT.
sheet.list_records(...)folio listPaginated list with optional projection / filter.
sheet.get_record(id)(sub-case of list)One record by primary key.
sheet.upsert_records([...])folio upsertInsert / update by primary key.
sheet.delete_records([...])folio deleteRemove by primary key.

Plus the materialize trio:

SDKCLI
sheet.materialize(...)folio materialize
sheet.materialization_status(targets=...)folio status
sheet.provenance(record_id, field, history=False)folio provenance

And the script bridge:

SDKCLI
sheet.run_script(name, *, argument)folio script run
folio.scripts.discover_scripts(sheet_path)folio script list

Type hints everywhere

Every public function is fully typed. Sheet, Contract, Property, AIDerivation, etc. are Pydantic v2 models with strict validation. mypy / pyright pick them up out of the box.

Errors

The exception hierarchy:

FolioError
├── ContractError # contract.yaml invalid
├── SheetError # sheet directory invalid / not found
├── RecordsError # records.jsonl unreadable
├── QueryError # SELECT-only violation, DuckDB rejection
├── OperationError # write-time validation failure
│ └── PermissionDeniedError # x-editable-by mismatch
├── DerivationError # derivation file invalid / cycle
└── LockTimeoutError # could not acquire .lock within 30 s

See Errors for which method raises which exception.

Threading and processes

A single Sheet instance is not thread-safe (don’t call mutating methods from two threads). Across processes, the .lock file serializes writers (ADR-0006). Reads do not take the lock and may interleave with writes — readers always see a consistent atomic snapshot thanks to the temp-file-and-rename pattern.

Testing

Folio’s own test suite is the canonical example:

  • Inject StubAIClient to keep ai derivations offline.
  • Inject StubHTTPTransport to keep http derivations offline.
  • Patch folio._cache.default_cache_root to a tmp_path so tests don’t share state with the user cache.
  • Patch folio.scripts.runtime_root_for_sheet similarly when testing the python kind with a venv.

See tests/test_materialize.py for end-to-end patterns.

See also