Skip to main content

formspec (Python)

src/formspec/ — Python tooling backend. Uses the Rust/PyO3 runtime for FEL parsing, evaluation, and dependency extraction, plus Python-side lint orchestration, adapters, mapping helpers, changelog generation, and registry access.

Entry point: src/formspec/ (namespace package; import from subpackages directly) No re-exports from __init__.py — use from formspec.fel import ..., from formspec.validator import ..., etc.


FEL — src/formspec/fel/

Rust-backed FEL runtime contract for Python. The legacy pure-Python parser/evaluator stack has been removed.

Quick Start

from formspec.fel import evaluate, parse, extract_dependencies, to_python

parsed = parse("$price * $quantity")  # syntax validation + opaque handle

result = evaluate("$price * $quantity", {"price": 10, "quantity": 3})
print(to_python(result.value))  # Decimal('30')
print(result.diagnostics)       # []

deps = extract_dependencies("sum($items[*].cost) + $base")
print(deps.fields)              # {'items.cost', 'base'}
print(deps.has_wildcard)        # True

Public API (fel/__init__.py)

evaluate(source: str, data: dict | None = None, *,
         instances: dict[str, dict] | None = None,
         mip_states: dict[str, object] | None = None,
         extensions: dict[str, object] | None = None,
         variables: dict[str, FelValue] | None = None) -> EvalResult

extract_dependencies(source: str) -> DependencySet
parse(source: str) -> ParsedExpression        # opaque handle; raises FelSyntaxError

default_fel_runtime() -> RustFelRuntime
builtin_function_catalog() -> list[dict[str, str]]
BUILTIN_NAMES: frozenset[str]
RESERVED_WORDS: frozenset[str]

FelNull, FelTrue, FelFalse
FelNumber(value: Decimal)
FelString(value: str)
FelBoolean(value: bool)
FelDate(value: date | datetime)
FelArray(elements: tuple)
FelMoney(amount: Decimal, currency: str)
FelObject(fields: dict)
FelValue

fel_bool(v) -> FelBoolean
from_python(val) -> FelValue
to_python(val: FelValue)
typeof(val: FelValue) -> str
is_null(val) -> bool

FelError, FelSyntaxError, FelDefinitionError, FelEvaluationError
Diagnostic(message: str, pos: SourcePos | None, severity: Severity)
SourcePos(offset: int, line: int, col: int)
Severity.ERROR, Severity.WARNING

Runtime Contract

  • parse() performs syntax validation and returns ParsedExpression(source=...). Python does not receive a public AST anymore.
  • evaluate() and extract_dependencies() call the mandatory formspec_rust PyO3 module.
  • builtin_function_catalog() and BUILTIN_NAMES are exported from Rust metadata.
  • Dynamic Python FEL extensions are no longer supported. register_extension(...) remains only to reject the removed contract explicitly.

Type System (fel/types.py)

Every FEL value remains a frozen Python dataclass wrapper. from_python() and to_python() still convert between Python-native values and the public FEL value types, including {amount, currency} money objects.

Dependency Extraction

@dataclass
class DependencySet:
    fields: set[str]
    instance_refs: set[str]
    context_refs: set[str]
    mip_deps: set[str]
    has_self_ref: bool
    has_wildcard: bool
    uses_prev_next: bool

extract_dependencies() returns the Rust-generated static dependency set used by the validator and evaluator.


Validator / Static Linter — src/formspec/validator/

Quick Start

# One-shot
from formspec.validator import lint
diagnostics = lint(document, mode="strict")

# Full linter instance
from formspec.validator import FormspecLinter, make_policy
linter = FormspecLinter(policy=make_policy("strict"))
diagnostics = linter.lint(doc, component_definition=def_doc)

CLI

# Authoring mode (default), text output
python -m formspec.validator definition.json

# Strict CI mode, JSON output
python -m formspec.validator --mode strict --format json definition.json

# Schema-only validation
python -m formspec.validator --schema-only definition.json

# Skip FEL checks
python -m formspec.validator --no-fel definition.json

# Lint component document with definition cross-reference
python -m formspec.validator --definition def.json component.json

# GitHub Actions annotation format
python -m formspec.validator --format github definition.json

Exit code: 1 if errors, 0 if clean, 2 for input file issues.

Diagnostic Type

class LintDiagnostic(msgspec.Struct, frozen=True):
    code: str                      # e.g., "E100", "E300", "W704"
    severity: str                  # "error" | "warning" | "info"
    path: str                      # JSONPath location (e.g., "$.items[0].binds[1]")
    message: str                   # human-readable description
    suggested_fix: str | None = None   # machine-readable repair hint for the authoring loop
    spec_ref: str | None = None        # pointer to the normative spec clause (e.g., "specs/core/spec.md#bind-target")

suggested_fix and spec_ref are populated opportunistically by individual rules; diagnostics that do not provide them leave the fields as None. They exist so LLMs consuming diagnostics can apply structured fixes and trace each rule back to the spec, rather than parsing prose.

Lint Modes

  • authoring — passes diagnostics through unchanged; lenient for interactive editing.
  • strict — escalates specific warnings to errors for CI: W800 (unresolved bind refs), W802 (compatibility fallback), W803 (duplicate editable bindings), W804 (duplicate bind in component tree).

Pipeline Passes

  1. Schema validation (always) — jsonschema Draft202012Validator against the appropriate schema. The Python validator currently auto-discovers and validates 11 artifact kinds: definition, response, intake_handoff, validation_report, validation_result, mapping, registry, theme, component, changelog, fel_functions.
  2. Document type detection — sentinel keys: $formspec → definition, $formspecResponse → response, $formspecIntakeHandoff → intake_handoff, $formspecTheme → theme, $formspecComponent → component, $formspecRegistry → registry, $formspecValidationReport → validation_report, $formspecValidationResult → validation_result, $formspecChangelog → changelog, $formspecFelFunctions → fel_functions.
  3. Structural error gate — structural schema errors halt further passes.
  4. For definition documents:
    • Tree indexing (item key/path index, duplicate detection)
    • Reference integrity (bind paths, shape targets, optionSet refs)
    • FEL expression compilation (parse all FEL in binds/shapes/screener)
    • Dependency analysis (graph, cycle detection)
  5. For theme documents: Token value validation
  6. For component documents: Component semantic checks

Diagnostic Code Reference

Code Severity Category Description
E100 error schema Unknown document type
E101 error schema JSON Schema validation error
E200 error tree Duplicate item key
E201 error tree Duplicate item path
E300 error reference Bind path does not resolve
E301 error reference Shape target does not resolve
E302 error reference Undefined optionSet
W300 warning reference dataType incompatible with optionSet
E400 error expression Invalid FEL syntax
E500 error dependency Dependency cycle
W700 warning theme Invalid color token
W701 warning theme Invalid spacing/size token
W702 warning theme Invalid font weight token
W703 warning theme Unitless line-height expected
W704 warning theme Undefined token reference
E800 error component Root must be layout component
E801 error component Undefined custom component
E802 error component Incompatible component/dataType
W802 warning component Fallback compatibility only
E803 error component Missing options source
E804 error component Richtext requires string field
E806 error component Missing custom component params
E807 error component Custom component cycle
W800 warning component Unresolved bind path
W801 warning component Layout/container should not bind
W803 warning component Duplicate editable binding
W804 warning component Duplicate bind in component tree

Component Compatibility Matrix (validator/component_matrix.py)

Maps 14 input components to their allowed dataTypes in strict and authoring modes: TextInput, NumberInput, DatePicker, Select, CheckboxGroup, Toggle, FileUpload, RadioGroup, MoneyInput, Slider, Rating, Signature.

def classify_compatibility(component_name: str, data_type: str) -> CompatibilityStatus
def requires_options_source(component_name: str) -> bool

Adapters — src/formspec/adapters/

Bidirectional format adapters. Each implements:

class Adapter(ABC):
    @abstractmethod
    def serialize(self, value: JsonValue) -> bytes
    @abstractmethod
    def deserialize(self, data: bytes) -> JsonValue

def get_adapter(format: str, config: dict | None = None, target_schema: dict | None = None) -> Adapter
def register_adapter(prefix: str, adapter_class: type) -> None  # prefix must start with 'x-'

JsonAdapter

Config: pretty (bool), sortKeys (bool), nullHandling ("include" | "omit"). nullHandling="omit" recursively strips None-valued keys.

XmlAdapter

Config: declaration (bool, default true), indent (int, default 2), cdata (list of paths to wrap in CDATA).

@-prefixed keys become XML attributes; lists become repeated sibling elements; dicts become nested elements. Deserialization auto-detects repeated siblings as arrays. Supports namespace registration.

CsvAdapter

Config: delimiter (str, default ,), quote (str, default "), header (bool, default true), encoding (str, default "utf-8"), lineEnding ("crlf" | "lf", default "crlf").

Accepts a list of flat dicts, a single flat dict, or a dict with one list-valued key (repeat group expansion — scalars duplicate across rows). RFC 4180 compliant.


Mapping Engine — src/formspec/mapping/

from formspec.mapping import MappingEngine

engine = MappingEngine(mapping_doc)
target = engine.forward(response_data)   # Response → Target format
source = engine.reverse(target_data)    # Target → Response format

Mapping Document Shape

  • rules: list of MappingRule (sorted by priority descending)
  • defaults: Record<string, any> applied to forward output
  • autoMap: bool — copy unmentioned source fields
  • direction: str
  • targetSchema: dict

Rule Structure

{
  "sourcePath": "a.b.c",    # dot-notation with bracket indices
  "targetPath": "x.y.z",
  "transform": "preserve",  # preserve | valueMap | coerce | constant | drop | expression | ...
  "condition": "source.field = value",  # or != variant
  "priority": 10,
  "reversePriority": 5,
  "reverse": { ... }        # Partial rule override for reverse direction
}

Array Descriptor Modes

  • whole — treat entire array as single value
  • each — apply transform per element
  • indexed — map by positional index

Transform Types

Transform Description
preserve Copy unchanged; supports default
drop Discard value
expression Evaluate FEL expression
coerce Type conversion: string, number, integer, boolean, date, array, object
valueMap Lookup table; unmapped handling: error / passthrough / drop / default
flatten Nested object → flat string
nest Flat string → nested object
constant FEL expression ignoring source
concat FEL expression for concatenation
split FEL expression for splitting

Condition guards evaluate FEL with $source and $target in the environment. valueMap auto-inverts for reverse if no explicit reverse mapping.


Changelog Generation — src/formspec/changelog.py

from formspec.changelog import generate_changelog

changelog = generate_changelog(old_def: dict, new_def: dict, definition_url: str) -> dict

Compares two definition documents and produces a changelog conforming to changelog.schema.json.

Diff targets: items (by key), binds (by path), shapes (by name), optionSets, dataSources, screener, migrations, metadata keys.

Impact classification:

  • Items: added → compatible, removed → breaking, type change → breaking, label-only → cosmetic
  • Binds: added with required → breaking, removed → breaking, added/removed required → breaking/compatible
  • Shapes: added → compatible, removed → compatible (loosens constraints)
  • optionSets/dataSources: added → compatible, removed → breaking

Semver impact: major if any breaking, minor if any compatible, patch otherwise.

Output: { definitionUrl, fromVersion, toVersion, generatedAt, semverImpact, changes: [{ type, target, path, impact, key?, before?, after?, description?, migrationHint? }] }


Definition Evaluator — src/formspec/evaluator.py

Server-side form processor. Runs four phases per submission: rebuild (init) → recalculate → revalidate → apply non-relevant behavior (NRB).

from formspec.evaluator import DefinitionEvaluator, ProcessingResult

ev = DefinitionEvaluator(definition)
result = ev.process(submitted_data)   # ProcessingResult
results = ev.validate(submitted_data) # list[dict] convenience

ProcessingResult

@dataclass
class ProcessingResult:
    valid: bool
    results: list[dict]         # Validation results
    data: dict                  # Processed response data
    variables: dict[str, FelValue]
    counts: dict[str, int]      # Repeat group instance counts

Instantiate DefinitionEvaluator once per definition; call process() for each submission. Accepts optional registries: list[Registry] for extension constraint validation. Also provides evaluate_screener(answers) for pre-form screening logic.


Extension Registry — src/formspec/registry.py

from formspec.registry import Registry, validate_lifecycle_transition

reg = Registry(registry_doc)
entry = reg.find_one("x-my-component", version=">=1.0.0", status="stable")
entries = reg.find("x-comp", version=">=1.0.0 <2.0.0", category="component")
entries = reg.list_by_category("component")
errors = reg.validate()  # Returns list of error strings
valid = validate_lifecycle_transition("draft", "stable")  # True

RegistryEntry fields: name, category, version, status, description, compatibility, publisher, spec_url, schema_url, license, deprecation_notice, base_type, parameters, returns, members.

Valid statuses: draft, stable, deprecated, retired.

Lifecycle transitions: draft, stable, or deprecated can transition to any other status. retired is terminal.

Registry.find supports semver constraints (e.g., ">=1.0.0 <2.0.0"), sorts by version descending.

Registry.validate checks: extension name pattern (x-[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*), deprecated entries have notices, dataType entries have baseType, function entries have parameters and returns.

WELL_KNOWN_PATH = '/.well-known/formspec-extensions'


Artifact Validator — src/formspec/validate.py

Auto-discovers and validates all Formspec JSON artifacts in a directory. Runs 10 passes that exercise the full toolchain: linting, schema validation, runtime evaluation, mapping, changelog generation, registry checks, and FEL expression parsing.

CLI

python3 -m formspec.validate path/to/artifacts/
python3 -m formspec.validate path/to/artifacts/ --registry common.registry.json
python3 -m formspec.validate path/to/artifacts/ --title "My Project"

Library API

from formspec.validate import discover_artifacts, validate_all, print_report

artifacts = discover_artifacts(Path("my-project/"))
report = validate_all(artifacts)
sys.exit(print_report(report))  # 0 = success, >0 = error count

Validation Passes

  1. Definition linting
  2. Sidecar linting
  3. Theme linting
  4. Component linting
  5. Response schema validation
  6. Runtime evaluation (via DefinitionEvaluator)
  7. Mapping forward transform
  8. Changelog generation
  9. Registry validation
  10. FEL expression parsing

Each pass returns a PassResult with per-item success/failure and diagnostics. print_report() renders colored terminal output.


Architectural Patterns

  • Frozen dataclasses everywhere — AST nodes, diagnostics, FEL values, and type wrappers freeze for safe sharing and hashability.
  • Singletons — FelNull, FelTrue, FelFalse, _DROP_SENTINEL enable identity comparison.
  • Special-form functions — Functions that need unevaluated AST (e.g., if(), countWhere(), MIP functions, repeat navigation) receive the evaluator and AST nodes rather than pre-evaluated arguments.
  • propagate_null flag on FuncDef — triggers automatic null propagation before invocation. Aggregates, type-checkers, and casts set propagate_null=False for custom null handling.
  • Multi-pass linter — Schema validation gates semantic analysis; structural errors halt further passes. Each pass lives in a separate module with defined inputs and outputs.
  • Policy-driven severity — The authoring/strict split transforms diagnostics after each pass; check modules themselves stay mode-agnostic.
  • Adapter abstraction — The Adapter ABC (serialize/deserialize) decouples the mapping engine from wire formats. Custom adapters use the x- prefix.
  • Environment scoping — Let-bindings and countWhere element bindings use a push/pop scope stack for lexical scoping without mutation.

Metadata

Release files for formspec-py 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for formspec-py 0.2.0
File Size Uploaded
formspec_py-0.2.0.tar.gz 1.3 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for formspec-py 0.2.0
File
formspec_py-0.2.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
formspec_py-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
formspec_py-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
formspec_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
formspec_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 13.0 MB

Release files / formspec_py-0.2.0.tar.gz

Download URL formspec_py-0.2.0.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
2c070ae6b5e46ebbc23a605beefc644573b4799a72553753008c08e11496ded8
BLAKE2b-256 checksum
How to use checksums
679192c8f596dfc2fd98a95ce8f2ad0276b0be362ee3055bc343b8dc5967cbf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / formspec_py-0.2.0-cp310-abi3-win_amd64.whl

Download URL formspec_py-0.2.0-cp310-abi3-win_amd64.whl
Size 2.4 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
6a9fe3d69beddf2793ce90b72997e03517229e9e24ed6a54ee5fe6d3843e8631
BLAKE2b-256 checksum
How to use checksums
05c585d6bb0b93dcb378ab960d5e882c007e0294af61b21123c17c39c6c378c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / formspec_py-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL formspec_py-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.4 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
8d9bf147c78afdb3607f3f4ec48e3b7d16027c244b3aeda427a8e5b6c3d136da
BLAKE2b-256 checksum
How to use checksums
a972e21f6bfd5e61adb35cbfc70c904c1a13a47a85b0054e8e8525d38621eefe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / formspec_py-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL formspec_py-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.3 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
4f18ed0b3ef8191751c0bd984591bca1dd2e01bb927d17db7437a7ef32dc584f
BLAKE2b-256 checksum
How to use checksums
0c0ce95ed48fb3e266f04fde97cc7cbef84663185140f25f933b4e5c84a47ebc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / formspec_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL formspec_py-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
Size 2.3 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d263dea6bff9fca7064ef850ba3d4054d14c79d286f43e58707c94627baadaf1
BLAKE2b-256 checksum
How to use checksums
2a212218a522de2dde27148061d35e98b172286cc0dccd6932c7dcc12615396b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release files / formspec_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL formspec_py-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 2.4 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
ec13401aba43affc89fcff9b46e44e02e6adb44ffc4b0f7e37d9c648e7e585c9
BLAKE2b-256 checksum
How to use checksums
d250bb71266051b4d7e28214c81781e5fd7ec1b3f5aa205fc06d4c843375d38a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

6 release files

0.1.0

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page