[emit]
[emit]class-default = "dataclass"format = trueskip-decoration-bases = []model-extra = "forbid"traceback-remap = false| Key | Type | Default | Description |
|---|---|---|---|
class-default | "dataclass" | "dataclass" | Default emission target for class declarations. Only "dataclass" is accepted today; use the per-class model keyword for Pydantic. |
format | bool | true | Post-process emitted .py through ruff format. |
skip-decoration-bases | list of strings | [] | Extra base-class names that suppress the automatic @dataclass(slots=True) decorator. |
model-extra | "forbid" | "ignore" | "allow" | "forbid" | Value for ConfigDict(extra=…) injected into every model class. |
traceback-remap | bool | false | Inject a runtime sys.excepthook into the entry module so uncaught-exception tracebacks point at .ty source instead of emitted .py. |
class-default
"dataclass"—class Foo:emits@dataclass(slots=True). This is the only accepted value today: performance-cheap, no PyPI dependency.
A project-wide Pydantic default (class-default = "pydantic") is not supported — it is rejected at config load. Choose Pydantic per class with the model keyword instead (model Foo: emits a BaseModel(extra="forbid")); you can mix class and model freely in the same project.
Any other value ("pydantic", "struct", "plain", "none", the empty string, …) is rejected at config load with tyc::invalid_config_value and the allowed-values list.
format
When true (default), emitted .py is post-processed through ruff format for consistent style. Off is occasionally useful when you want to see the raw printer output for debugging.
skip-decoration-bases
The compiler already auto-skips @dataclass(slots=True) for the stdlib bases that conflict with it (Enum, IntEnum, StrEnum, Flag, IntFlag, ABC, ABCMeta), plus Protocol, TypedDict, and NamedTuple. skip-decoration-bases extends that list with project-specific bases:
[emit]skip-decoration-bases = ["BaseModel", "App", "TestCase"]The match is on the last identifier segment, so "App" catches both class T(App): and class T(textual.App): regardless of how the base is imported. With this list set, you can write the plain class keyword for framework subclasses without reaching for class! or plain class.
Auto-skip drops only the decorator — it does not synthesise an __init__. Reach for class! Foo(Base): when you also want a synthesised constructor calling super().__init__(), or plain class Foo: for “no decorator, no synthesised init, body emits verbatim.”
model-extra
Controls the extra argument in the auto-injected model_config = ConfigDict(extra=…) for every model class.
| Value | Runtime behaviour |
|---|---|
"forbid" (default) | Unknown fields raise ValidationError. Recommended for public APIs. |
"ignore" | Unknown fields are silently dropped. |
"allow" | Unknown fields are accepted and surfaced as extra attributes on the model instance. |
[emit]model-extra = "allow" # trust all incoming JSONThe value affects every model class in the project. To override for a single class, write model_config = ConfigDict(extra="…") directly in the class body — the desugarer skips injection when a model_config statement is already present.
traceback-remap
Off by default. When true, the emitter injects typhon_runtime.traceback.install() at the top of the entry module’s if __name__ == "__main__": block. The installed sys.excepthook loads the emitted .py.map sidecars and rewrites each frame of an uncaught-exception traceback from the emitted .py back to the corresponding .ty source — the same mapping tyc trace applies, but automatically and without a separate command.
[emit]traceback-remap = trueWhen the entry module has no __main__ guard — a script that simply calls main() at the bottom, which most single-file programs are — one is synthesised, placed after the module docstring and imports and ahead of the first executable statement. The knob is never silently inert.
Every row of the traceback is rewritten, not just the File "…", line N header: the source row CPython prints under each frame is replaced with the real .ty row, and the column anchors (~~~~^^^^), whose columns no longer line up, are dropped. Without that, a frame reading File "src/main.ty", line 10 could sit above a line of emitted Python — including a generated name like __typhon_qi_0__ — that does not exist at main.ty:10. Where the .ty file cannot be read (a build shipped without its sources) the original row is kept rather than dropped.
It only touches the entry script — library imports never trip the __main__ guard, so imported modules are left byte-for-byte unchanged. On any failure (missing sidecar, malformed map) the hook falls back to the previously-installed sys.excepthook, so it can only ever improve a traceback, never suppress one.
Always-on behaviour
One emit policy that was previously documented is no longer configurable:
- PEP 561
.pyistubs are always emitted for every.dtyfile.
Where next
- Classes → dataclass (lowering) — what
classandmodelcompile to. - Pydantic Boundary Models — when to choose
model.