Interactive editor for Pydantic models — produces config.yaml/.toml/.json
Project description
pydantic-studio
Interactive editor for Pydantic models. Generate and edit config.yaml /
config.toml / config.json against a strongly-typed schema, with three
frontends sharing a single form-state model: a Textual TUI, an HTMX-driven
local web app, and a CLI shorthand.
Why?
Hand-editing config files is error-prone. Pydantic schemas already encode the contract — types, constraints, defaults, descriptions. pydantic-studio turns that schema into an editor, with format round-trip that preserves your hand-written comments.
Status
v0.1.0 — Alpha. All 9 implementation phases are merged on master. Production code paths are exercised by 416 tests (unit + integration + TUI/HTMX smoke). API is stable enough for early adopters; expect v0.2 to add polish (Tailwind pipeline, theme toggle, status-bar UI) without breaking the public API.
Install
pip install pydantic-studio
# or
uv add pydantic-studio
For EmailStr support, install with the email extra:
pip install 'pydantic-studio[email]'
Quick start
Programmatic
from pydantic import BaseModel, Field, HttpUrl, SecretStr
from pydantic_studio import build_form_tree, save_yaml
class AppSettings(BaseModel):
name: str = Field(default="prod", description="Service identifier")
port: int = Field(default=8080, ge=1, le=65535, description="Listening port")
api_url: HttpUrl = Field(default=HttpUrl("https://api.example.com"))
api_key: SecretStr = Field(default=SecretStr("change-me"))
tree = build_form_tree(AppSettings)
tree.set_value("port", 9090)
save_yaml(tree, "config.yaml")
# Service identifier
name: prod
# Listening port
port: 9090
api_url: https://api.example.com
api_key: change-me
CLI
# Stub a fresh config from defaults
pydantic-studio fill myapp.config:AppSettings --out config.yaml
# Validate without launching anything
pydantic-studio check myapp.config:AppSettings config.yaml
# Print the validated model
pydantic-studio run myapp.config:AppSettings config.yaml
# Open the Textual TUI
pydantic-studio edit myapp.config:AppSettings config.yaml
# Or the HTMX-driven browser UI
pydantic-studio edit --frontend web myapp.config:AppSettings config.yaml
Format is auto-detected from extension (.yaml / .yml / .toml / .json).
Textual TUI
| Key | Action |
|---|---|
Ctrl+S |
Save (writes via save_yaml; refuses on validation failure) |
Ctrl+Z / Ctrl+Y |
Undo / redo |
Ctrl+Q |
Quit (confirms if dirty) |
Browser UI
--frontend web boots a local FastAPI app on a random free port and
opens your browser. Edits POST to HTMX endpoints; the preview pane
updates live. Closing the tab triggers a 30-second heartbeat timeout
(configurable via run_html_app(..., heartbeat_timeout_seconds=...))
and the server shuts down.
Type coverage
| Family | Types |
|---|---|
| Primitives | str, int, float, bool, Decimal |
| Choices | Enum, Literal[...] |
| Containers | list[T], set[T], tuple[T, ...], tuple[T1, T2, ...], dict[K, V] |
| Unions | T | U, Optional[T] |
| Temporal | datetime, date, time, timedelta |
| Network | IPv4Address, IPv6Address, IPv4Network, IPv6Network, AnyUrl/HttpUrl/FileUrl, EmailStr |
| Special | pathlib.Path, uuid.UUID, SecretStr, SecretBytes, re.Pattern, bytes |
| Constraints | Pydantic v2 Annotated constraints (ge/le/min_length/pattern/etc.) — auto-wired |
Add custom types via register_builder(MyBuilder()).
File-format support
| Format | Read | Write | User-comment round-trip |
|---|---|---|---|
| YAML | ruamel.yaml |
ruamel.yaml |
✓ |
| TOML | tomllib (stdlib) |
tomlkit |
description comments only |
| JSON | stdlib json |
model_dump_json(indent=2) |
n/a (JSON has no comments) |
from pydantic_studio import load_config, save_config
tree = load_config("config.toml", AppSettings) # picks parser by extension
tree.set_value("port", 9090)
save_config(tree, "config.toml") # picks writer by extension
Format-specific helpers — load_yaml / save_yaml, load_toml /
save_toml, load_json / save_json, save_draft_yaml (skips
validation for mid-edit drafts) — are also exported.
Public API surface
from pydantic_studio import (
# Tree construction
build_form_tree, FormTree, FormNode,
# 24 node types
StringNode, IntNode, FloatNode, BoolNode, DecimalNode,
DatetimeNode, DateNode, TimeNode, TimedeltaNode,
IpAddressNode, IpNetworkNode, UrlNode, EmailNode,
PathNode, UuidNode, SecretNode, PatternNode, BytesNode,
EnumNode, LiteralNode, SequenceNode, MappingNode, UnionNode, GroupNode,
# I/O
load_config, save_config,
load_yaml, save_yaml, save_draft_yaml,
load_toml, save_toml,
load_json, save_json,
# Drafts
save_draft, load_draft, delete_draft, find_draft, draft_newer_than,
# Renderers
StudioApp, run_app, # Textual TUI
StudioServer, run_html_app, # HTML/HTMX
# Registry
Registry, NodeBuilder, register_builder,
default_registry, reset_default_registry,
# Validation + exceptions
ValidationResult,
PydanticStudioError, NoBuilderError,
CancelledByUser, ValidationFailedError,
)
Drafts (auto-save + recovery)
from pydantic_studio import find_draft, load_draft, save_draft, delete_draft
# Mid-session
save_draft(tree, ".pydantic-studio.draft.json")
# On the next launch
existing = find_draft(".")
if existing is not None:
tree = load_draft(existing, MyConfig)
# ... user resumes editing ...
delete_draft(existing) # on successful submit
save_draft_yaml(tree, path) is the YAML equivalent — it skips
to_instance() validation so partial trees can persist mid-edit.
Architecture
┌──────────────────────────────────────────────────────────┐
│ Textual TUI │ HTML browser │ CLI │ ← renderers │
│ (StudioApp) │ (StudioServer)│ │ (frontends) │
└──────┬───────────┬────────────┬──┘ │
│ │ │ │
▼ ▼ ▼ │
┌──────────────────────────────────────────────────────────┐│
│ FormTree (canonical state) ││
│ • 24 node types, discriminated by `kind` ││
│ • path-addressed mutations (set_value / add_item / …) ││
│ • snapshot ring for undo / redo ││
│ • validate-first contract ││
└──────────────────────┬───────────────────────────────────┘│
│ │
▼ │
┌──────────────────────────────────────────────────────────┐│
│ I/O layer ││
│ • load_config / save_config (extension dispatch) ││
│ • YAML / TOML / JSON specific helpers ││
│ • Draft persistence ││
└──────────────────────────────────────────────────────────┘│
│
Type registry (NodeBuilder Protocol) — extend with │
`register_builder(MyBuilder())` for custom Pydantic │
types the default registry doesn't recognize. │
The full architecture doc is at docs/site/architecture.md. Run
uv run mkdocs serve to read it locally.
Development
git clone https://github.com/pydantic-studio/pydantic-studio
cd pydantic-studio
uv sync
# Tests
uv run pytest -q # 416 tests
uv run pytest tests/unit/test_yaml_io.py # focused
# Lint
uv run ruff check
uv run pyright src/pydantic_studio # production code only
# Docs
uv run mkdocs serve # 127.0.0.1:8000
uv run mkdocs build --strict # also covered by test_docs_build.py
Project conventions are documented in CLAUDE.md — the
guide for AI-assisted development sessions, but useful for any
contributor.
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydantic_studio-0.1.0.tar.gz.
File metadata
- Download URL: pydantic_studio-0.1.0.tar.gz
- Upload date:
- Size: 69.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1c039b0c5775296520d2169c66ea98c3d039908e680269130b0453168ff3e70
|
|
| MD5 |
a40e1eee293b9da5bbad5dc76bcb9572
|
|
| BLAKE2b-256 |
4d4d210833ce00246422b5eb262594b735f3ba04d61b784d04caaf3e0fd48617
|
File details
Details for the file pydantic_studio-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pydantic_studio-0.1.0-py3-none-any.whl
- Upload date:
- Size: 92.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c33e99b99a1ea64b1ff765c29a8b015111be29f4b7fe5bde6d8579eafe3def5b
|
|
| MD5 |
1d0d3c4bf8c874fbdbc0c7fbe8abbb54
|
|
| BLAKE2b-256 |
12ab50eb2db68f680c50c6ddba76a69186ef3e263ce09dc0801d9c8d4a8dcd13
|