overture-schema-codegen 0.1.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- overture/schema/codegen/__init__.py +1 -0
- overture/schema/codegen/cli.py +228 -0
- overture/schema/codegen/extraction/__init__.py +0 -0
- overture/schema/codegen/extraction/docstring.py +46 -0
- overture/schema/codegen/extraction/enum_extraction.py +40 -0
- overture/schema/codegen/extraction/examples.py +367 -0
- overture/schema/codegen/extraction/field.py +172 -0
- overture/schema/codegen/extraction/field_constraints.py +185 -0
- overture/schema/codegen/extraction/field_walk.py +275 -0
- overture/schema/codegen/extraction/length_constraints.py +49 -0
- overture/schema/codegen/extraction/literal_alternatives.py +26 -0
- overture/schema/codegen/extraction/model_constraints.py +252 -0
- overture/schema/codegen/extraction/model_extraction.py +240 -0
- overture/schema/codegen/extraction/newtype_extraction.py +73 -0
- overture/schema/codegen/extraction/numeric_extraction.py +74 -0
- overture/schema/codegen/extraction/pydantic_extraction.py +33 -0
- overture/schema/codegen/extraction/specs.py +295 -0
- overture/schema/codegen/extraction/type_analyzer.py +693 -0
- overture/schema/codegen/extraction/type_registry.py +137 -0
- overture/schema/codegen/extraction/union_extraction.py +270 -0
- overture/schema/codegen/layout/__init__.py +0 -0
- overture/schema/codegen/layout/module_layout.py +139 -0
- overture/schema/codegen/layout/type_collection.py +122 -0
- overture/schema/codegen/markdown/__init__.py +0 -0
- overture/schema/codegen/markdown/link_computation.py +70 -0
- overture/schema/codegen/markdown/path_assignment.py +114 -0
- overture/schema/codegen/markdown/pipeline.py +198 -0
- overture/schema/codegen/markdown/renderer.py +641 -0
- overture/schema/codegen/markdown/reverse_references.py +169 -0
- overture/schema/codegen/markdown/templates/_used_by.md.jinja2 +10 -0
- overture/schema/codegen/markdown/templates/enum.md.jinja2 +13 -0
- overture/schema/codegen/markdown/templates/feature.md.jinja2 +45 -0
- overture/schema/codegen/markdown/templates/geometric.md.jinja2 +11 -0
- overture/schema/codegen/markdown/templates/newtype.md.jinja2 +17 -0
- overture/schema/codegen/markdown/templates/numeric.md.jinja2 +27 -0
- overture/schema/codegen/markdown/templates/pydantic_type.md.jinja2 +8 -0
- overture/schema/codegen/markdown/type_format.py +383 -0
- overture/schema/codegen/py.typed +0 -0
- overture/schema/codegen/pyspark/__init__.py +1 -0
- overture/schema/codegen/pyspark/_primitive_fill.py +23 -0
- overture/schema/codegen/pyspark/_render_common.py +477 -0
- overture/schema/codegen/pyspark/check_builder.py +961 -0
- overture/schema/codegen/pyspark/check_ir.py +223 -0
- overture/schema/codegen/pyspark/constraint_dispatch.py +753 -0
- overture/schema/codegen/pyspark/pipeline.py +220 -0
- overture/schema/codegen/pyspark/renderer.py +816 -0
- overture/schema/codegen/pyspark/schema_builder.py +187 -0
- overture/schema/codegen/pyspark/templates/_check_function.py.jinja2 +10 -0
- overture/schema/codegen/pyspark/templates/model_module.py.jinja2 +83 -0
- overture/schema/codegen/pyspark/templates/test_module.py.jinja2 +129 -0
- overture/schema/codegen/pyspark/test_data/__init__.py +9 -0
- overture/schema/codegen/pyspark/test_data/base_row.py +835 -0
- overture/schema/codegen/pyspark/test_data/constraint_values.py +203 -0
- overture/schema/codegen/pyspark/test_data/invalid_value.py +105 -0
- overture/schema/codegen/pyspark/test_data/scaffold.py +390 -0
- overture/schema/codegen/pyspark/test_renderer.py +708 -0
- overture/schema/codegen/spec_discovery.py +66 -0
- overture_schema_codegen-0.1.1.dev0.dist-info/METADATA +13 -0
- overture_schema_codegen-0.1.1.dev0.dist-info/RECORD +61 -0
- overture_schema_codegen-0.1.1.dev0.dist-info/WHEEL +4 -0
- overture_schema_codegen-0.1.1.dev0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Relative link computation between rendered output files."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
|
|
6
|
+
from overture.schema.system.case import to_snake_case
|
|
7
|
+
|
|
8
|
+
from ..extraction.specs import TypeIdentity
|
|
9
|
+
|
|
10
|
+
__all__ = ["LinkContext", "relative_link"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class LinkContext:
|
|
15
|
+
"""Placement context for resolving cross-directory markdown links."""
|
|
16
|
+
|
|
17
|
+
page_path: PurePosixPath
|
|
18
|
+
registry: dict[TypeIdentity, PurePosixPath]
|
|
19
|
+
|
|
20
|
+
def resolve_link(self, identity: TypeIdentity) -> str | None:
|
|
21
|
+
"""Resolve *identity* to a relative link if it exists in the registry."""
|
|
22
|
+
if identity in self.registry:
|
|
23
|
+
return relative_link(self.page_path, self.registry[identity])
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def resolve_link_or_slug(self, identity: TypeIdentity) -> str:
|
|
27
|
+
"""Resolve *identity* to a relative link, falling back to a slug filename.
|
|
28
|
+
|
|
29
|
+
Always returns a usable link string. Use when the caller needs a
|
|
30
|
+
link regardless of whether the type has a registered page.
|
|
31
|
+
"""
|
|
32
|
+
return self.resolve_link(identity) or f"{to_snake_case(identity.name)}.md"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_normalized(path: PurePosixPath) -> bool:
|
|
36
|
+
"""Check whether the path contains no '..' or '.' components (except root '.')."""
|
|
37
|
+
return ".." not in path.parts and path.parts.count(".") <= 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def relative_link(source: PurePosixPath, target: PurePosixPath) -> str:
|
|
41
|
+
"""Compute a relative path from source file to target file.
|
|
42
|
+
|
|
43
|
+
Both paths must be normalized (no `..` components) and relative
|
|
44
|
+
to the same output root.
|
|
45
|
+
"""
|
|
46
|
+
if not _is_normalized(source):
|
|
47
|
+
msg = f"Source path not normalized: {source}"
|
|
48
|
+
raise ValueError(msg)
|
|
49
|
+
if not _is_normalized(target):
|
|
50
|
+
msg = f"Target path not normalized: {target}"
|
|
51
|
+
raise ValueError(msg)
|
|
52
|
+
source_dir = source.parent
|
|
53
|
+
# Count how many levels up from source_dir to common ancestor,
|
|
54
|
+
# then descend to target. PurePosixPath doesn't have os.path.relpath,
|
|
55
|
+
# so compute manually.
|
|
56
|
+
source_parts = source_dir.parts
|
|
57
|
+
target_parts = target.parts
|
|
58
|
+
|
|
59
|
+
# Find common prefix length
|
|
60
|
+
common = 0
|
|
61
|
+
for s, t in zip(source_parts, target_parts, strict=False):
|
|
62
|
+
if s != t:
|
|
63
|
+
break
|
|
64
|
+
common += 1
|
|
65
|
+
|
|
66
|
+
ups = len(source_parts) - common
|
|
67
|
+
downs = target_parts[common:]
|
|
68
|
+
|
|
69
|
+
parts = [".."] * ups + list(downs)
|
|
70
|
+
return "/".join(parts) if parts else "."
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Map types to markdown output file paths.
|
|
2
|
+
|
|
3
|
+
Uses module-mirrored output directories: output paths derive from
|
|
4
|
+
the source Python module path relative to schema_root.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from pathlib import PurePosixPath
|
|
9
|
+
|
|
10
|
+
from overture.schema.system.case import to_snake_case
|
|
11
|
+
|
|
12
|
+
from ..extraction.specs import (
|
|
13
|
+
ModelSpec,
|
|
14
|
+
PydanticTypeSpec,
|
|
15
|
+
SupplementarySpec,
|
|
16
|
+
TypeIdentity,
|
|
17
|
+
)
|
|
18
|
+
from ..layout.module_layout import compute_output_dir, output_dir_for_entry_point
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"GEOMETRIC_PAGE",
|
|
22
|
+
"NUMERIC_PAGE",
|
|
23
|
+
"build_placement_registry",
|
|
24
|
+
"resolve_output_path",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Aggregate page paths.
|
|
28
|
+
NUMERIC_PAGE = PurePosixPath("system/numeric.md")
|
|
29
|
+
GEOMETRIC_PAGE = PurePosixPath("system/geometric.md")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_placement_registry(
|
|
33
|
+
model_specs: Sequence[ModelSpec],
|
|
34
|
+
all_specs: dict[TypeIdentity, SupplementarySpec],
|
|
35
|
+
numeric_names: list[TypeIdentity],
|
|
36
|
+
geometry_names: list[TypeIdentity],
|
|
37
|
+
schema_root: str,
|
|
38
|
+
) -> dict[TypeIdentity, PurePosixPath]:
|
|
39
|
+
"""Build a mapping from TypeIdentity to output file paths.
|
|
40
|
+
|
|
41
|
+
Uses module-mirrored output directories: output paths derive from
|
|
42
|
+
the source Python module path relative to schema_root.
|
|
43
|
+
"""
|
|
44
|
+
registry: dict[TypeIdentity, PurePosixPath] = _aggregate_page_entries(
|
|
45
|
+
numeric_names, geometry_names
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
feature_dirs: set[PurePosixPath] = set()
|
|
49
|
+
for spec in model_specs:
|
|
50
|
+
spec_dir = output_dir_for_entry_point(spec.entry_point, schema_root)
|
|
51
|
+
registry[spec.identity] = _md_path(spec_dir, spec.name)
|
|
52
|
+
feature_dirs.add(spec_dir)
|
|
53
|
+
|
|
54
|
+
for tid, supp_spec in all_specs.items():
|
|
55
|
+
if tid in registry:
|
|
56
|
+
continue
|
|
57
|
+
if isinstance(supp_spec, PydanticTypeSpec):
|
|
58
|
+
registry[tid] = _md_path(
|
|
59
|
+
PurePosixPath("pydantic") / supp_spec.source_module, tid.name
|
|
60
|
+
)
|
|
61
|
+
continue
|
|
62
|
+
source_module = getattr(supp_spec.source_type, "__module__", None)
|
|
63
|
+
if source_module is None:
|
|
64
|
+
continue
|
|
65
|
+
output_dir = compute_output_dir(source_module, schema_root)
|
|
66
|
+
output_dir = _nest_under_types(output_dir, feature_dirs)
|
|
67
|
+
registry[tid] = _md_path(output_dir, tid.name)
|
|
68
|
+
|
|
69
|
+
return registry
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve_output_path(
|
|
73
|
+
identity: TypeIdentity,
|
|
74
|
+
registry: dict[TypeIdentity, PurePosixPath] | None,
|
|
75
|
+
) -> PurePosixPath:
|
|
76
|
+
"""Look up a type's output path from the registry, with flat-file fallback."""
|
|
77
|
+
if registry is not None and identity in registry:
|
|
78
|
+
return registry[identity]
|
|
79
|
+
return _md_path(PurePosixPath(""), identity.name)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _aggregate_page_entries(
|
|
83
|
+
numeric_names: list[TypeIdentity],
|
|
84
|
+
geometry_names: list[TypeIdentity],
|
|
85
|
+
) -> dict[TypeIdentity, PurePosixPath]:
|
|
86
|
+
"""Pre-populate registry entries for types documented on aggregate pages."""
|
|
87
|
+
entries: dict[TypeIdentity, PurePosixPath] = dict.fromkeys(
|
|
88
|
+
numeric_names, NUMERIC_PAGE
|
|
89
|
+
)
|
|
90
|
+
entries.update(dict.fromkeys(geometry_names, GEOMETRIC_PAGE))
|
|
91
|
+
return entries
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _nest_under_types(
|
|
95
|
+
output_dir: PurePosixPath, feature_dirs: set[PurePosixPath]
|
|
96
|
+
) -> PurePosixPath:
|
|
97
|
+
"""Insert `types/` after the feature directory portion.
|
|
98
|
+
|
|
99
|
+
If *output_dir* equals or is a subdirectory of a feature directory,
|
|
100
|
+
returns a path with `types/` inserted after the feature directory.
|
|
101
|
+
Otherwise returns *output_dir* unchanged.
|
|
102
|
+
"""
|
|
103
|
+
for fd in sorted(feature_dirs, key=lambda p: len(p.parts), reverse=True):
|
|
104
|
+
try:
|
|
105
|
+
relative = output_dir.relative_to(fd)
|
|
106
|
+
except ValueError:
|
|
107
|
+
continue
|
|
108
|
+
return fd / "types" / relative
|
|
109
|
+
return output_dir
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _md_path(directory: PurePosixPath, name: str) -> PurePosixPath:
|
|
113
|
+
"""Build a .md file path from a directory and a PascalCase type name."""
|
|
114
|
+
return directory / f"{to_snake_case(name)}.md"
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Markdown generation pipeline: render pages without I/O.
|
|
2
|
+
|
|
3
|
+
Orchestrates tree expansion, type collection, placement, reverse
|
|
4
|
+
references, and rendering into a list of RenderedPage objects. The
|
|
5
|
+
caller decides what to do with them (write to disk, add frontmatter,
|
|
6
|
+
stream to stdout, etc.).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Mapping, Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import PurePosixPath
|
|
12
|
+
|
|
13
|
+
import overture.schema.system.geometric as _system_geometric
|
|
14
|
+
import overture.schema.system.numeric as _system_numeric
|
|
15
|
+
from overture.schema.system.geometric import GeometryType
|
|
16
|
+
|
|
17
|
+
from ..extraction.examples import ExampleRecord, load_examples
|
|
18
|
+
from ..extraction.numeric_extraction import extract_numerics
|
|
19
|
+
from ..extraction.specs import (
|
|
20
|
+
EnumSpec,
|
|
21
|
+
ModelSpec,
|
|
22
|
+
NewTypeSpec,
|
|
23
|
+
PydanticTypeSpec,
|
|
24
|
+
RecordSpec,
|
|
25
|
+
SupplementarySpec,
|
|
26
|
+
TypeIdentity,
|
|
27
|
+
UnionSpec,
|
|
28
|
+
)
|
|
29
|
+
from ..extraction.type_analyzer import is_newtype
|
|
30
|
+
from ..layout.type_collection import collect_all_supplementary_types
|
|
31
|
+
from .link_computation import LinkContext
|
|
32
|
+
from .path_assignment import (
|
|
33
|
+
GEOMETRIC_PAGE,
|
|
34
|
+
NUMERIC_PAGE,
|
|
35
|
+
build_placement_registry,
|
|
36
|
+
resolve_output_path,
|
|
37
|
+
)
|
|
38
|
+
from .renderer import (
|
|
39
|
+
render_enum,
|
|
40
|
+
render_geometry_from_values,
|
|
41
|
+
render_model,
|
|
42
|
+
render_newtype,
|
|
43
|
+
render_numeric_from_specs,
|
|
44
|
+
render_pydantic_type,
|
|
45
|
+
)
|
|
46
|
+
from .reverse_references import UsedByEntry, compute_reverse_references
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"RenderedPage",
|
|
50
|
+
"generate_markdown_pages",
|
|
51
|
+
"partition_numeric_and_geometry_types",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class RenderedPage:
|
|
57
|
+
"""A rendered page with its content and output path."""
|
|
58
|
+
|
|
59
|
+
content: str
|
|
60
|
+
path: PurePosixPath
|
|
61
|
+
is_model: bool = False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _load_model_examples(
|
|
65
|
+
spec: ModelSpec,
|
|
66
|
+
) -> list[ExampleRecord] | None:
|
|
67
|
+
"""Load examples for a feature spec, returning None when absent."""
|
|
68
|
+
if isinstance(spec, UnionSpec):
|
|
69
|
+
pyproject_source = spec.members[0] if spec.members else None
|
|
70
|
+
validation_type = spec.source_annotation
|
|
71
|
+
model_fields = spec.common_base.model_fields
|
|
72
|
+
else:
|
|
73
|
+
pyproject_source = spec.source_type
|
|
74
|
+
validation_type = spec.source_type
|
|
75
|
+
model_fields = spec.source_type.model_fields if spec.source_type else {}
|
|
76
|
+
if not pyproject_source:
|
|
77
|
+
return None
|
|
78
|
+
field_names = [f.name for f in spec.fields]
|
|
79
|
+
examples = load_examples(
|
|
80
|
+
validation_type,
|
|
81
|
+
spec.name,
|
|
82
|
+
field_names,
|
|
83
|
+
pyproject_source=pyproject_source,
|
|
84
|
+
model_fields=model_fields,
|
|
85
|
+
)
|
|
86
|
+
return examples or None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _render_supplement(
|
|
90
|
+
tid: TypeIdentity,
|
|
91
|
+
spec: SupplementarySpec,
|
|
92
|
+
registry: dict[TypeIdentity, PurePosixPath],
|
|
93
|
+
reverse_refs: dict[TypeIdentity, list[UsedByEntry]],
|
|
94
|
+
) -> RenderedPage:
|
|
95
|
+
"""Render a single supplementary type page."""
|
|
96
|
+
output_path = resolve_output_path(tid, registry)
|
|
97
|
+
ctx = LinkContext(output_path, registry)
|
|
98
|
+
used_by = reverse_refs.get(tid)
|
|
99
|
+
|
|
100
|
+
match spec:
|
|
101
|
+
case EnumSpec():
|
|
102
|
+
content = render_enum(spec, link_ctx=ctx, used_by=used_by)
|
|
103
|
+
case NewTypeSpec():
|
|
104
|
+
content = render_newtype(spec, ctx, used_by=used_by)
|
|
105
|
+
case RecordSpec():
|
|
106
|
+
content = render_model(spec, ctx, used_by=used_by)
|
|
107
|
+
case PydanticTypeSpec():
|
|
108
|
+
content = render_pydantic_type(spec, link_ctx=ctx, used_by=used_by)
|
|
109
|
+
case _:
|
|
110
|
+
raise TypeError(
|
|
111
|
+
f"Unhandled SupplementarySpec variant: {type(spec).__name__}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return RenderedPage(content=content, path=output_path)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def partition_numeric_and_geometry_types(
|
|
118
|
+
numeric_module: object,
|
|
119
|
+
geometric_module: object,
|
|
120
|
+
) -> tuple[list[TypeIdentity], list[TypeIdentity]]:
|
|
121
|
+
"""Discover numeric and geometry types from their source modules' exports.
|
|
122
|
+
|
|
123
|
+
NewType exports of *numeric_module* are numeric types.
|
|
124
|
+
Non-constraint class/enum exports of *geometric_module* are geometry types.
|
|
125
|
+
"""
|
|
126
|
+
numerics: list[TypeIdentity] = []
|
|
127
|
+
for name in getattr(numeric_module, "__all__", []):
|
|
128
|
+
obj = getattr(numeric_module, name)
|
|
129
|
+
if is_newtype(obj):
|
|
130
|
+
numerics.append(TypeIdentity(obj, name))
|
|
131
|
+
|
|
132
|
+
geometries: list[TypeIdentity] = []
|
|
133
|
+
for name in getattr(geometric_module, "__all__", []):
|
|
134
|
+
obj = getattr(geometric_module, name)
|
|
135
|
+
if isinstance(obj, type) and not name.endswith("Constraint"):
|
|
136
|
+
geometries.append(TypeIdentity(obj, name))
|
|
137
|
+
|
|
138
|
+
return numerics, geometries
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def generate_markdown_pages(
|
|
142
|
+
model_specs: Sequence[ModelSpec],
|
|
143
|
+
schema_root: str,
|
|
144
|
+
*,
|
|
145
|
+
external_specs: Mapping[TypeIdentity, SupplementarySpec] | None = None,
|
|
146
|
+
) -> list[RenderedPage]:
|
|
147
|
+
"""Generate all markdown pages from feature specs.
|
|
148
|
+
|
|
149
|
+
Returns rendered pages without writing to disk. The caller handles
|
|
150
|
+
I/O, frontmatter injection, and any output-format-specific concerns
|
|
151
|
+
(like Docusaurus category files).
|
|
152
|
+
|
|
153
|
+
`external_specs` are supplementary types documented on their own but not
|
|
154
|
+
reachable by walking feature field trees -- a `RootModel` entry point,
|
|
155
|
+
which serializes as its bare root value and so appears in no feature as
|
|
156
|
+
a named reference. They join the collected supplementary types and
|
|
157
|
+
render, place, and cross-reference identically.
|
|
158
|
+
"""
|
|
159
|
+
numeric_names, geometry_names = partition_numeric_and_geometry_types(
|
|
160
|
+
_system_numeric, _system_geometric
|
|
161
|
+
)
|
|
162
|
+
all_specs = collect_all_supplementary_types(model_specs)
|
|
163
|
+
if external_specs:
|
|
164
|
+
all_specs = {**all_specs, **external_specs}
|
|
165
|
+
registry = build_placement_registry(
|
|
166
|
+
model_specs, all_specs, numeric_names, geometry_names, schema_root
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
reverse_refs = compute_reverse_references(model_specs, all_specs)
|
|
170
|
+
|
|
171
|
+
pages: list[RenderedPage] = []
|
|
172
|
+
|
|
173
|
+
for spec in model_specs:
|
|
174
|
+
output_path = registry[spec.identity]
|
|
175
|
+
ctx = LinkContext(output_path, registry)
|
|
176
|
+
examples = _load_model_examples(spec)
|
|
177
|
+
used_by = reverse_refs.get(spec.identity)
|
|
178
|
+
content = render_model(spec, link_ctx=ctx, examples=examples, used_by=used_by)
|
|
179
|
+
pages.append(RenderedPage(content=content, path=output_path, is_model=True))
|
|
180
|
+
|
|
181
|
+
for tid, supp_spec in all_specs.items():
|
|
182
|
+
pages.append(_render_supplement(tid, supp_spec, registry, reverse_refs))
|
|
183
|
+
|
|
184
|
+
pages.append(
|
|
185
|
+
RenderedPage(
|
|
186
|
+
content=render_numeric_from_specs(extract_numerics(numeric_names)),
|
|
187
|
+
path=NUMERIC_PAGE,
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
pages.append(
|
|
192
|
+
RenderedPage(
|
|
193
|
+
content=render_geometry_from_values([m.value for m in GeometryType]),
|
|
194
|
+
path=GEOMETRIC_PAGE,
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return pages
|