config: The Configuration Layer (myform.yaml + [tool.myform] + .py extensions)#
One typed schema with per-format applicability tags, projected two ways: the mdformat-facing
MystOptions surface stays authoritative for the plugin facade, while MyformConfig adds the
translator-wide knobs (degradation policy, assists, per-target sections). Python extension modules
register additional readers, writers, and lowerings through the same registries used internally.
Integration seam for myform.convert: resolve config = MyformConfig.load(target_path), call
load_extensions(config) before resolving the target writer (an extension may register the
target format itself), then warn_inapplicable(config, dst) before rendering.
This package is PEP 562-lazy: the pydantic schema (options) and the extension loader
(extensions) import on first attribute access, so the stdlib-only discovery walk stays
importable from cold paths like the CLI’s mdformat fast-path check.
The Configuration Layer (myform.yaml + [tool.myform] + .py extensions).
One typed schema with per-format applicability tags, projected two ways: the mdformat-facing
MystOptions surface stays authoritative for the plugin facade, while MyformConfig adds the
translator-wide knobs (degradation policy, assists, per-target sections). Python extension modules
register additional readers, writers, and lowerings through the same registries used internally.
Integration seam for myform.convert: resolve config = MyformConfig.load(target_path), call
load_extensions(config) before resolving the target writer (an extension may register the
target format itself), then warn_inapplicable(config, dst) before rendering.
This package is PEP 562-lazy: the pydantic schema (options) and the extension loader
(extensions) import on first attribute access, so the stdlib-only discovery walk stays
importable from cold paths like the CLI’s mdformat fast-path check.
- class myform.config.AssistConfig(*, enabled: bool = False, model: str = 'bulletin', base_url: str = 'http://localhost:4000', api_key_env: Annotated[str, MinLen(min_length=1)] = 'LITELLM_TOKEN', timeout: Annotated[float, Gt(gt=0)] = 5.0, cache: bool = True)#
The
assist:section – opt-in, degrade-only LM suggestions (myform.assist).- enabled: bool#
zero network calls when
False.- Type:
Enable the gateway-backed assist. Off by default
- model: str#
Gateway model alias used for assist requests (a light/cheap tier by default).
- base_url: str#
Base URL of the OpenAI-compatible local gateway.
- api_key_env: str#
Environment variable carrying the local gateway’s client bearer token.
- timeout: float#
Socket timeout for one opt-in gateway request, in seconds.
- cache: bool#
Memoize assist responses on disk, keyed by
(node hash, target format, model).
- class myform.config.DegradeConfig(*, mode: 'annotate' | 'silent' | 'strict' = 'annotate', comments: bool = True)#
The
degrade:section – the degradation engine’s policy knobs (myform.degrade).- mode: 'annotate' | 'silent' | 'strict'#
How the degradation engine reacts to a non-
NATIVE(APPROX/CARRY/DROP) node.
- comments: bool#
Leave a provenance comment (e.g. rST
.. myform: ...) for replaced/dropped nodes.
- class myform.config.MyformConfig(*, degrade: DegradeConfig = <factory>, assist: AssistConfig = <factory>, myst: MystOptions = <factory>, gfm: dict[str, ~typing.Any]=<factory>, rst: RstConfig = <factory>, typst: TypstConfig = <factory>, extensions: list[str] = <factory>)#
The whole-translator configuration schema (
myform.yaml/[tool.myform]).Instances are frozen and reject unknown top-level keys (once warned about, see
from_mapping). Build one withload(file discovery) orfrom_mapping(a mapping you already parsed), never the bare constructor with untrusted input – the bare constructor skips the warn-on-unknown-key and reuse-MystOptions.from_optionsbehavior.- degrade: DegradeConfig#
consulted for any target whose writer capability table is not all-
NATIVE.- Type:
Degradation engine policy. Cross-cutting
- assist: AssistConfig#
LM assist policy. Cross-cutting, same reasoning as
degrade.
- myst: MystOptions#
MyST reader/writer knobs – the mdformat-facing
MystOptionssurface, reused directly.
- gfm: dict[str, Any]#
GFM reader/writer knobs. No typed
GfmOptionsschema exists yet –mdformat-gfmexposes no analogous[plugin.gfm]surface in this codebase – so this is a raw pass-through mapping merged into the mdformat options for thegfmextension; it becomes a typed model mirroringmystonce one lands.
- typst: TypstConfig#
Typst writer knobs.
- extensions: list[str]#
dotted module paths or
.pyfile paths. Seemyform.config.extensions.load_extensions.- Type:
Extension entries
- classmethod from_mapping(raw: Mapping[str, Any]) Self#
Build a validated
MyformConfigfrom a raw config mapping (parsed YAML/TOML).Same philosophy as
MystOptions.from_optionsat both this and each section’s level:Nonevalues are dropped so field defaults apply; an unknown top-level section is warned about and ignored (never aborts a run over a stray/typo’d key); a wrong value type still raises, since that is a genuine misconfiguration. Only sections present in raw are passed to the constructor, somodel_fields_seton the result exactly reflects what the config provided –warn_inapplicabledepends on this.
- classmethod load(start: Path) Self#
Discover the nearest myform config upward from start and build a validated instance.
Precedence within one directory:
myform.yaml, then.myform.yaml, then[tool.myform]inpyproject.toml(apyproject.tomlwithout that table does not count as “found”). The nearest directory holding any of the three wins over a farther one, regardless of which file type it is. Nothing found by the filesystem root -> defaults.
- classmethod discover(start: Path) tuple[dict[str, Any], Path | None]#
Walk upward from start and return the raw config mapping plus its source path.
start may be the target file itself or its containing directory. Returns
({}, None)when no config file is found. Exposed separately fromloadso discovery precedence is directly testable without also exercising validation. The walk itself lives inmyform.config.discovery.discover_config, shared with the CLI’s fast-path check.
- classmethod json_schema() dict[str, Any]#
Return the JSON Schema for the whole
MyformConfig(editor autocomplete/validation).
- class myform.config.Registries(Reader: type[Reader], Writer: type[Writer], degrade: LoweringRegistry)#
The registry namespace handed to an extension’s
register(registries)hook.ReaderandWriterare the exact registry classes this package’s own reader/writer modules decorate through (@Reader.register/@Writer.register); an extension registers exactly the same way.degradeis the exact process-wide lowering registry consulted bylower.
- class myform.config.RstConfig(*, heading_underline_ladder: Annotated[str, MinLen(min_length=1)] = '#*=-^"')#
The
rst:section – Sphinx reStructuredText writer knobs (launch-minimal).- heading_underline_ladder: str#
Underline characters for nested heading levels, outermost first (docutils convention).
- class myform.config.TypstConfig(*, prelude: 'none' | 'myform' = 'none', postformat: 'off' | 'auto' | 'require' = 'off')#
The
typst:section – Typst writer knobs (see the Typst-ecosystem memo, spec §8).- prelude: 'none' | 'myform'#
Emit calls into the small shipped
myform.typprelude for MyST-only constructs.
- postformat: 'off' | 'auto' | 'require'#
Shell out to
typstylefor final normalization, if present.
- myform.config.discover_config(start: Path) tuple[Path, dict[str, Any] | None] | None#
Walk upward from start and return the winning config source, or
None.start may be the target file itself or its containing directory. Precedence within one directory:
myform.yaml, then.myform.yaml, then[tool.myform]inpyproject.toml(apyproject.tomlwithout that table does not count as “found”). The nearest directory holding any of the three wins over a farther one, regardless of which file type it is.The result is a
(path, raw)pair. For apyproject.tomlwinner raw is the parsed[tool.myform]table – read here, exactly once, so no check-then-act re-read can observe a different file. For a YAML winner raw isNoneand the caller parses the file through its own YAML layer.
- myform.config.load_extensions(config: MyformConfig) None#
Import and register every
config.extensionsentry.- Parameters:
config – The loaded
MyformConfig;MyformConfig.extensionsentries are dotted module paths (e.g.mypackage.myform_ext) or.pyfile paths (e.g../myform_ext.py).- Raises:
ImportError – An entry could not be imported (bad path, bad module, syntax error).
AttributeError – A successfully imported module has no callable
register.
- myform.config.warn_inapplicable(config: MyformConfig, target_format: str) None#
Warn for every explicitly-configured section that cannot affect target_format.
Consults only the top-level sections the config actually set (
config.model_fields_set, populated byMyformConfig.from_mappingto exactly mirror what was in the file) against each field’s declaredapplies_totag. Cross-cutting sections (DegradeConfig,AssistConfig,extensions) are tagged withALL_FORMATSand never warn. This never raises – a mistagged option is a loud log line, not a crashed render (the dredge-note contract).- Parameters:
config – A
MyformConfigbuilt viaload/from_mapping(somodel_fields_setis meaningful; a bareMyformConfig()has an empty set and never warns).target_format – The format name being written (e.g.
'gfm').