Skip to main content

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.

status python tests


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

pydantic_studio-0.1.0.tar.gz (69.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydantic_studio-0.1.0-py3-none-any.whl (92.4 kB view details)

Uploaded Python 3

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

Hashes for pydantic_studio-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f1c039b0c5775296520d2169c66ea98c3d039908e680269130b0453168ff3e70
MD5 a40e1eee293b9da5bbad5dc76bcb9572
BLAKE2b-256 4d4d210833ce00246422b5eb262594b735f3ba04d61b784d04caaf3e0fd48617

See more details on using hashes here.

File details

Details for the file pydantic_studio-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_studio-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c33e99b99a1ea64b1ff765c29a8b015111be29f4b7fe5bde6d8579eafe3def5b
MD5 1d0d3c4bf8c874fbdbc0c7fbe8abbb54
BLAKE2b-256 12ab50eb2db68f680c50c6ddba76a69186ef3e263ce09dc0801d9c8d4a8dcd13

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page