|
| 1 | +"""ADR-0021 D3 — stable-name generator registry (Python port). |
| 2 | +
|
| 3 | +Generators are identified by a STABLE string id (e.g. ``entity``, ``routes``, |
| 4 | +``render-helper``) rather than by a language-specific factory import. The id is |
| 5 | +the cross-port contract: the same logical generator carries the same stable name |
| 6 | +in every port. This module is the discoverability + identity surface behind |
| 7 | +``metaobjects gen --list`` and the ``--generators a,b`` selection path. |
| 8 | +
|
| 9 | +It is ADDITIVE. The default suite in ``cli.py`` (``_default_generators``) and the |
| 10 | +``run_gen(..., generators=[...])`` factory-array path keep working unchanged — the |
| 11 | +registry powers ``--list`` and stable identity; it does not replace those paths. |
| 12 | +
|
| 13 | +The registry's name set is conformance-tested for SET EQUALITY against the Python |
| 14 | +slice of the canonical manifest |
| 15 | +(``fixtures/generator-registry-conformance/registry.json``): exactly the manifest |
| 16 | +entries whose ``ports`` array includes ``python``. All Python entries are |
| 17 | +``tier: native``. |
| 18 | +""" |
| 19 | +from __future__ import annotations |
| 20 | + |
| 21 | +from dataclasses import dataclass |
| 22 | +from typing import Callable |
| 23 | + |
| 24 | +from metaobjects.codegen.generator import Generator |
| 25 | +from metaobjects.codegen.generators.entity_model import entity_model |
| 26 | +from metaobjects.codegen.generators.extractor_generator import extractor_generator |
| 27 | +from metaobjects.codegen.generators.filter_allowlist_generator import ( |
| 28 | + filter_allowlist_generator, |
| 29 | +) |
| 30 | +from metaobjects.codegen.generators.output_parser_generator import ( |
| 31 | + output_parser_generator, |
| 32 | +) |
| 33 | +from metaobjects.codegen.generators.output_prompt_generator import ( |
| 34 | + output_prompt_generator, |
| 35 | +) |
| 36 | +from metaobjects.codegen.generators.payload_vo_generator import payload_vo_generator |
| 37 | +from metaobjects.codegen.generators.render_helper_generator import ( |
| 38 | + render_helper_generator, |
| 39 | +) |
| 40 | +from metaobjects.codegen.generators.router_generator import router_generator |
| 41 | +from metaobjects.codegen.generators.template_generator import template_generator |
| 42 | +from metaobjects.render.verify import InMemoryProvider |
| 43 | + |
| 44 | +GeneratorTier = str # "native" | "neutral" |
| 45 | + |
| 46 | + |
| 47 | +@dataclass(frozen=True) |
| 48 | +class GeneratorEntry: |
| 49 | + """A registry entry: stable name + one-line description + tier + factory.""" |
| 50 | + |
| 51 | + #: Stable, cross-port-consistent id. Equals the registry map key. |
| 52 | + name: str |
| 53 | + #: One-line (no newline) human description for ``--list``. |
| 54 | + description: str |
| 55 | + #: "native" = recommended ``metaobjects gen`` suite; "neutral" = ``meta docs``-owned. |
| 56 | + tier: GeneratorTier |
| 57 | + #: Constructs the generator with sensible defaults. Calling it must not throw. |
| 58 | + factory: Callable[[], Generator] |
| 59 | + |
| 60 | + |
| 61 | +def _template_primitive() -> Generator: |
| 62 | + """A no-op default for the ``template`` PRIMITIVE generator. |
| 63 | +
|
| 64 | + ``template_generator`` requires caller-supplied ``template`` / ``walk`` / |
| 65 | + ``provider`` (it is not a zero-config per-entity emitter). For registry |
| 66 | + identity + ``--list`` we expose a default that constructs a valid Generator |
| 67 | + without throwing and walks to zero outputs; real use passes opts via the |
| 68 | + factory-array config path. Mirrors the TS ``templatePrimitive()``. |
| 69 | + """ |
| 70 | + return template_generator( |
| 71 | + name="template", |
| 72 | + template="", |
| 73 | + walk=lambda _root: [], |
| 74 | + provider=InMemoryProvider(), |
| 75 | + ) |
| 76 | + |
| 77 | + |
| 78 | +def _render_helper_default() -> Generator: |
| 79 | + """Construct ``render-helper`` with a default ``template_root``. |
| 80 | +
|
| 81 | + The factory ctor only requires ``template_root`` to be non-empty (it builds a |
| 82 | + ``FilesystemProvider`` lazily; no disk access at construction). Real use passes |
| 83 | + the caller's on-disk template root via the factory-array config path; this |
| 84 | + default exists only so registry identity + ``--list`` construct without throwing. |
| 85 | + """ |
| 86 | + return render_helper_generator(template_root="templates") |
| 87 | + |
| 88 | + |
| 89 | +#: Stable name -> GeneratorEntry. The 9 native generators whose manifest `ports` |
| 90 | +#: include `python` (ADR-0021 D3). Set-equality conformance-tested vs the manifest. |
| 91 | +GENERATOR_REGISTRY: dict[str, GeneratorEntry] = { |
| 92 | + "entity": GeneratorEntry( |
| 93 | + name="entity", |
| 94 | + description="Per-entity model/class — the entity module (table-backed or value object).", |
| 95 | + tier="native", |
| 96 | + factory=entity_model, |
| 97 | + ), |
| 98 | + "routes": GeneratorEntry( |
| 99 | + name="routes", |
| 100 | + description="Per-entity REST endpoint surface (controllers / routes / router).", |
| 101 | + tier="native", |
| 102 | + factory=router_generator, |
| 103 | + ), |
| 104 | + "output-parser": GeneratorEntry( |
| 105 | + name="output-parser", |
| 106 | + description="Per-template tolerant output parser (recover-on-receipt).", |
| 107 | + tier="native", |
| 108 | + factory=output_parser_generator, |
| 109 | + ), |
| 110 | + "output-prompt": GeneratorEntry( |
| 111 | + name="output-prompt", |
| 112 | + description="Per-template output-format prompt fragment generator.", |
| 113 | + tier="native", |
| 114 | + factory=output_prompt_generator, |
| 115 | + ), |
| 116 | + "render-helper": GeneratorEntry( |
| 117 | + name="render-helper", |
| 118 | + description="Per-template.output render helper (document/email typed wrappers).", |
| 119 | + tier="native", |
| 120 | + factory=_render_helper_default, |
| 121 | + ), |
| 122 | + "extractor": GeneratorEntry( |
| 123 | + name="extractor", |
| 124 | + description="Per-template strict typed extract<Name> helper (strict payload extraction).", |
| 125 | + tier="native", |
| 126 | + factory=extractor_generator, |
| 127 | + ), |
| 128 | + "template": GeneratorEntry( |
| 129 | + name="template", |
| 130 | + description="Generic Mustache template primitive (walk + template -> files).", |
| 131 | + tier="native", |
| 132 | + factory=_template_primitive, |
| 133 | + ), |
| 134 | + "filter-allowlist": GeneratorEntry( |
| 135 | + name="filter-allowlist", |
| 136 | + description="Per-entity REST filter allowlist (queryable-field guard).", |
| 137 | + tier="native", |
| 138 | + factory=filter_allowlist_generator, |
| 139 | + ), |
| 140 | + "payload": GeneratorEntry( |
| 141 | + name="payload", |
| 142 | + description="Per-template payload value object (the strict payload type).", |
| 143 | + tier="native", |
| 144 | + factory=payload_vo_generator, |
| 145 | + ), |
| 146 | +} |
| 147 | + |
| 148 | + |
| 149 | +def list_generators() -> list[GeneratorEntry]: |
| 150 | + """All registry entries, sorted by stable name.""" |
| 151 | + return sorted(GENERATOR_REGISTRY.values(), key=lambda e: e.name) |
| 152 | + |
| 153 | + |
| 154 | +def get_generator(name: str) -> GeneratorEntry | None: |
| 155 | + """Resolve a generator entry by its stable id, or ``None`` if unknown.""" |
| 156 | + return GENERATOR_REGISTRY.get(name) |
0 commit comments