Skip to content

Commit fd5e698

Browse files
Add FeatureMetadata to schema feature classes for doc generation (#2945)
* Add FeatureMetadata to schema feature classes for doc generation * docs: update llms.txt files Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent dedd3a9 commit fd5e698

File tree

4 files changed

+299
-26
lines changed

4 files changed

+299
-26
lines changed

docs/llms-full.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24192,7 +24192,7 @@ datamodel-code-generator detects the OpenAPI version from the `openapi` field:
2419224192
| `deprecated` | 2019-09 | ⚠️ Partial | Recognized but not enforced |
2419324193
| `examples` (array) | Draft 6 | ⚠️ Partial | Only first example used for Field default |
2419424194
| Recursive `$ref` | Draft 4+ | ⚠️ Partial | Supported with `ForwardRef`, may require manual adjustment |
24195-
| `propertyNames` | Draft 6 | ❌ Not supported | Property name validation ignored |
24195+
| `propertyNames` | Draft 6 | ✅ Supported | Dict key type constraints via pattern, enum, or $ref |
2419624196
| `dependentRequired` | 2019-09 | ❌ Not supported | Dependent requirements ignored |
2419724197
| `dependentSchemas` | 2019-09 | ❌ Not supported | Dependent schemas ignored |
2419824198

docs/supported_formats.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ datamodel-code-generator detects the OpenAPI version from the `openapi` field:
138138
| `deprecated` | 2019-09 | ⚠️ Partial | Recognized but not enforced |
139139
| `examples` (array) | Draft 6 | ⚠️ Partial | Only first example used for Field default |
140140
| Recursive `$ref` | Draft 4+ | ⚠️ Partial | Supported with `ForwardRef`, may require manual adjustment |
141-
| `propertyNames` | Draft 6 | ❌ Not supported | Property name validation ignored |
141+
| `propertyNames` | Draft 6 | ✅ Supported | Dict key type constraints via pattern, enum, or $ref |
142142
| `dependentRequired` | 2019-09 | ❌ Not supported | Dependent requirements ignored |
143143
| `dependentSchemas` | 2019-09 | ❌ Not supported | Dependent schemas ignored |
144144

scripts/build_schema_docs.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""Schema documentation builder.
2+
3+
Generates feature tables from JsonSchemaFeatures and OpenAPISchemaFeatures
4+
metadata for docs/supported_formats.md.
5+
6+
Usage:
7+
python scripts/build_schema_docs.py # Generate/update docs
8+
python scripts/build_schema_docs.py --check # Check if docs are up to date
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import argparse
14+
import sys
15+
from dataclasses import fields
16+
from pathlib import Path
17+
18+
# Add src to path for imports
19+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
20+
21+
from datamodel_code_generator.parser.schema_version import (
22+
FeatureMetadata,
23+
JsonSchemaFeatures,
24+
OpenAPISchemaFeatures,
25+
)
26+
27+
DOCS_PATH = Path(__file__).parent.parent / "docs" / "supported_formats.md"
28+
29+
# Status emoji mapping
30+
STATUS_EMOJI = {
31+
"supported": "✅",
32+
"partial": "⚠️",
33+
"not_supported": "❌",
34+
}
35+
36+
37+
def get_feature_metadata(cls: type) -> list[tuple[str, FeatureMetadata]]:
38+
"""Extract feature metadata from a dataclass.
39+
40+
Args:
41+
cls: JsonSchemaFeatures or OpenAPISchemaFeatures class.
42+
43+
Returns:
44+
List of (field_name, metadata) tuples.
45+
"""
46+
result = []
47+
for f in fields(cls):
48+
if f.metadata:
49+
meta = FeatureMetadata(
50+
introduced=f.metadata.get("introduced", ""),
51+
doc_name=f.metadata.get("doc_name", f.name),
52+
description=f.metadata.get("description", ""),
53+
status=f.metadata.get("status", "supported"),
54+
)
55+
result.append((f.name, meta))
56+
return result
57+
58+
59+
def generate_feature_table(
60+
features: list[tuple[str, FeatureMetadata]],
61+
*,
62+
include_status: bool = True,
63+
) -> str:
64+
"""Generate a Markdown table from feature metadata.
65+
66+
Args:
67+
features: List of (field_name, metadata) tuples.
68+
include_status: Whether to include the Status column.
69+
70+
Returns:
71+
Markdown table string.
72+
"""
73+
if include_status:
74+
lines = ["| Feature | Introduced | Status | Description |"]
75+
lines.append("|---------|------------|--------|-------------|")
76+
for _name, meta in features:
77+
emoji = STATUS_EMOJI.get(meta["status"], "")
78+
status_text = f"{emoji} {meta['status'].replace('_', ' ').title()}"
79+
lines.append(f"| `{meta['doc_name']}` | {meta['introduced']} | {status_text} | {meta['description']} |")
80+
else:
81+
lines = ["| Feature | Introduced | Description |"]
82+
lines.append("|---------|------------|-------------|")
83+
for _name, meta in features:
84+
lines.append(f"| `{meta['doc_name']}` | {meta['introduced']} | {meta['description']} |")
85+
86+
return "\n".join(lines)
87+
88+
89+
def print_features_summary() -> None:
90+
"""Print a summary of all features with their metadata."""
91+
print("=" * 60)
92+
print("JSON Schema Features")
93+
print("=" * 60)
94+
for name, meta in get_feature_metadata(JsonSchemaFeatures):
95+
print(f" {name}:")
96+
print(f" doc_name: {meta['doc_name']}")
97+
print(f" introduced: {meta['introduced']}")
98+
print(f" status: {meta['status']}")
99+
print(f" description: {meta['description']}")
100+
print()
101+
102+
print("=" * 60)
103+
print("OpenAPI Schema Features (additional)")
104+
print("=" * 60)
105+
# Get only OpenAPI-specific fields (not inherited from JsonSchemaFeatures)
106+
json_field_names = {f.name for f in fields(JsonSchemaFeatures)}
107+
for name, meta in get_feature_metadata(OpenAPISchemaFeatures):
108+
if name not in json_field_names:
109+
print(f" {name}:")
110+
print(f" doc_name: {meta['doc_name']}")
111+
print(f" introduced: {meta['introduced']}")
112+
print(f" status: {meta['status']}")
113+
print(f" description: {meta['description']}")
114+
print()
115+
116+
117+
def generate_supported_features_table() -> str:
118+
"""Generate the supported features table for documentation."""
119+
lines = [
120+
"<!-- BEGIN AUTO-GENERATED SUPPORTED FEATURES -->",
121+
"",
122+
"### Supported Features (from code)",
123+
"",
124+
"The following features are tracked in the codebase with their implementation status:",
125+
"",
126+
"#### JSON Schema Features",
127+
"",
128+
generate_feature_table(get_feature_metadata(JsonSchemaFeatures)),
129+
"",
130+
"#### OpenAPI-Specific Features",
131+
"",
132+
]
133+
134+
# Get only OpenAPI-specific fields
135+
json_field_names = {f.name for f in fields(JsonSchemaFeatures)}
136+
openapi_features = [
137+
(name, meta) for name, meta in get_feature_metadata(OpenAPISchemaFeatures) if name not in json_field_names
138+
]
139+
lines.extend((generate_feature_table(openapi_features), "", "<!-- END AUTO-GENERATED SUPPORTED FEATURES -->"))
140+
141+
return "\n".join(lines)
142+
143+
144+
def main() -> int:
145+
"""Parse arguments and build documentation."""
146+
parser = argparse.ArgumentParser(description="Build schema documentation from code metadata")
147+
parser.add_argument(
148+
"--check",
149+
action="store_true",
150+
help="Check if docs would change without modifying files",
151+
)
152+
parser.add_argument(
153+
"--summary",
154+
action="store_true",
155+
help="Print a summary of all features and their metadata",
156+
)
157+
args = parser.parse_args()
158+
159+
if args.summary:
160+
print_features_summary()
161+
return 0
162+
163+
print("Schema Documentation Builder")
164+
print("-" * 40)
165+
166+
# For now, just print the generated table
167+
print("\nGenerated Supported Features Table:")
168+
print()
169+
print(generate_supported_features_table())
170+
171+
if args.check:
172+
print("\n[Check mode] No files modified.")
173+
else:
174+
print("\n[Info] This script currently outputs to stdout.")
175+
print(" Future versions will update docs/supported_formats.md directly.")
176+
177+
return 0
178+
179+
180+
if __name__ == "__main__":
181+
sys.exit(main())

src/datamodel_code_generator/parser/schema_version.py

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,117 @@
77

88
from __future__ import annotations
99

10-
from dataclasses import dataclass
11-
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
10+
from dataclasses import dataclass, field
11+
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar
12+
13+
from typing_extensions import TypedDict
1214

1315
from datamodel_code_generator.enums import JsonSchemaVersion, OpenAPIVersion
1416

1517
if TYPE_CHECKING:
1618
from datamodel_code_generator.types import Types
1719

1820

21+
class FeatureMetadata(TypedDict):
22+
"""Metadata for schema feature documentation.
23+
24+
This metadata is used by scripts/build_schema_docs.py to generate
25+
the feature compatibility matrix in docs/supported_formats.md.
26+
"""
27+
28+
introduced: str
29+
"""Version when feature was introduced (e.g., "Draft 6", "2020-12", "OAS 3.0")."""
30+
doc_name: str
31+
"""Display name for documentation (e.g., "prefixItems", "Null in type array")."""
32+
description: str
33+
"""User-facing description of the feature."""
34+
status: Literal["supported", "partial", "not_supported"]
35+
"""Implementation status: supported, partial, or not_supported."""
36+
37+
1938
@dataclass(frozen=True)
2039
class JsonSchemaFeatures:
2140
"""Feature flags for JSON Schema versions.
2241
2342
This is the base class for schema features. OpenAPISchemaFeatures
2443
extends this to add OpenAPI-specific features.
2544
26-
Attributes:
27-
null_in_type_array: Draft 2020-12 allows null in type arrays.
28-
defs_not_definitions: Draft 2019-09+ uses $defs instead of definitions.
29-
prefix_items: Draft 2020-12 uses prefixItems instead of items array.
30-
boolean_schemas: Draft 6+ allows boolean values as schemas.
31-
id_field: The field name for schema ID ("id" for Draft 4, "$id" for Draft 6+).
32-
definitions_key: The key for definitions ("definitions" or "$defs").
33-
exclusive_as_number: Draft 6+ uses numeric exclusiveMin/Max (Draft 4 uses boolean).
34-
read_only_write_only: Draft 7+ supports readOnly/writeOnly keywords.
45+
Each field includes metadata for documentation generation.
46+
Use `dataclasses.fields(JsonSchemaFeatures)` to access metadata.
3547
"""
3648

37-
null_in_type_array: bool
38-
defs_not_definitions: bool
39-
prefix_items: bool
40-
boolean_schemas: bool
41-
id_field: str
42-
definitions_key: str
43-
exclusive_as_number: bool
44-
read_only_write_only: bool
49+
null_in_type_array: bool = field(
50+
default=False,
51+
metadata=FeatureMetadata(
52+
introduced="2020-12",
53+
doc_name="Null in type array",
54+
description="Allows `type: ['string', 'null']` syntax for nullable types",
55+
status="supported",
56+
),
57+
)
58+
defs_not_definitions: bool = field(
59+
default=False,
60+
metadata=FeatureMetadata(
61+
introduced="2019-09",
62+
doc_name="$defs",
63+
description="Uses `$defs` instead of `definitions` for schema definitions",
64+
status="supported",
65+
),
66+
)
67+
prefix_items: bool = field(
68+
default=False,
69+
metadata=FeatureMetadata(
70+
introduced="2020-12",
71+
doc_name="prefixItems",
72+
description="Tuple validation using `prefixItems` keyword",
73+
status="supported",
74+
),
75+
)
76+
boolean_schemas: bool = field(
77+
default=False,
78+
metadata=FeatureMetadata(
79+
introduced="Draft 6",
80+
doc_name="Boolean schemas",
81+
description="Allows `true` and `false` as valid schemas",
82+
status="supported",
83+
),
84+
)
85+
id_field: str = field(
86+
default="$id",
87+
metadata=FeatureMetadata(
88+
introduced="Draft 6",
89+
doc_name="$id",
90+
description="Schema identifier field (`id` in Draft 4, `$id` in Draft 6+)",
91+
status="supported",
92+
),
93+
)
94+
definitions_key: str = field(
95+
default="$defs",
96+
metadata=FeatureMetadata(
97+
introduced="Draft 4",
98+
doc_name="definitions/$defs",
99+
description="Key for reusable schema definitions",
100+
status="supported",
101+
),
102+
)
103+
exclusive_as_number: bool = field(
104+
default=False,
105+
metadata=FeatureMetadata(
106+
introduced="Draft 6",
107+
doc_name="exclusiveMinimum/Maximum as number",
108+
description="Numeric `exclusiveMinimum`/`exclusiveMaximum` (boolean in Draft 4)",
109+
status="supported",
110+
),
111+
)
112+
read_only_write_only: bool = field(
113+
default=False,
114+
metadata=FeatureMetadata(
115+
introduced="Draft 7",
116+
doc_name="readOnly/writeOnly",
117+
description="Field visibility hints for read-only and write-only properties",
118+
status="supported",
119+
),
120+
)
45121

46122
@classmethod
47123
def from_version(cls, version: JsonSchemaVersion) -> JsonSchemaFeatures:
@@ -110,13 +186,28 @@ class OpenAPISchemaFeatures(JsonSchemaFeatures):
110186
111187
Extends JsonSchemaFeatures with OpenAPI-specific features.
112188
113-
Attributes:
114-
nullable_keyword: OpenAPI 3.0 uses nullable: true (deprecated in 3.1).
115-
discriminator_support: All OpenAPI versions support discriminator.
189+
Each field includes metadata for documentation generation.
190+
Use `dataclasses.fields(OpenAPISchemaFeatures)` to access metadata.
116191
"""
117192

118-
nullable_keyword: bool
119-
discriminator_support: bool
193+
nullable_keyword: bool = field(
194+
default=False,
195+
metadata=FeatureMetadata(
196+
introduced="OAS 3.0",
197+
doc_name="nullable",
198+
description="Uses `nullable: true` for nullable types (deprecated in 3.1)",
199+
status="supported",
200+
),
201+
)
202+
discriminator_support: bool = field(
203+
default=True,
204+
metadata=FeatureMetadata(
205+
introduced="OAS 3.0",
206+
doc_name="discriminator",
207+
description="Polymorphism support via `discriminator` keyword",
208+
status="supported",
209+
),
210+
)
120211

121212
@classmethod
122213
def from_openapi_version(cls, version: OpenAPIVersion) -> OpenAPISchemaFeatures:
@@ -310,6 +401,7 @@ def get_data_formats(*, is_openapi: bool = False) -> DataFormatMapping:
310401

311402
__all__ = [
312403
"DataFormatMapping",
404+
"FeatureMetadata",
313405
"JsonSchemaFeatures",
314406
"OpenAPISchemaFeatures",
315407
"SchemaFeaturesT",

0 commit comments

Comments
 (0)