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 returnsParsedExpression(source=...). Python does not receive a public AST anymore.evaluate()andextract_dependencies()call the mandatoryformspec_rustPyO3 module.builtin_function_catalog()andBUILTIN_NAMESare 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
- Schema validation (always) —
jsonschemaDraft202012Validatoragainst 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. - 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. - Structural error gate — structural schema errors halt further passes.
- For
definitiondocuments:- 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)
- For
themedocuments: Token value validation - For
componentdocuments: 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 ofMappingRule(sorted byprioritydescending)defaults:Record<string, any>applied to forward outputautoMap: bool — copy unmentioned source fieldsdirection: strtargetSchema: 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 valueeach— apply transform per elementindexed— 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
- Definition linting
- Sidecar linting
- Theme linting
- Component linting
- Response schema validation
- Runtime evaluation (via
DefinitionEvaluator) - Mapping forward transform
- Changelog generation
- Registry validation
- 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_SENTINELenable 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_nullflag onFuncDef— triggers automatic null propagation before invocation. Aggregates, type-checkers, and casts setpropagate_null=Falsefor 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
AdapterABC (serialize/deserialize) decouples the mapping engine from wire formats. Custom adapters use thex-prefix. - Environment scoping — Let-bindings and
countWhereelement 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)
| File | Size | Uploaded | |
|---|---|---|---|
| formspec_py-0.2.0.tar.gz | 1.3 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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