Skip to content

Commit abac763

Browse files
Add schema-docs auto-generation with pre-commit and CI (#2949)
* Add schema-docs auto-generation with pre-commit and CI * docs: update llms.txt files Generated by GitHub Actions * Add schema-docs to lint SKIP list * Improve conditional git handling in schema-docs workflow --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent fd5e698 commit abac763

File tree

7 files changed

+208
-20
lines changed

7 files changed

+208
-20
lines changed

.github/workflows/lint.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- uses: actions/setup-python@v5
3535
with:
3636
python-version: "3.14"
37-
- run: SKIP=readme,config-types uvx prek run --all-files --show-diff-on-failure
37+
- run: SKIP=readme,config-types,schema-docs uvx prek run --all-files --show-diff-on-failure
3838
- if: |
3939
github.event_name == 'push' ||
4040
github.event.pull_request.head.repo.full_name == github.repository ||

.github/workflows/schema-docs.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Update Schema Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'src/datamodel_code_generator/parser/schema_version.py'
8+
- 'scripts/build_schema_docs.py'
9+
- 'docs/supported_formats.md'
10+
pull_request:
11+
branches: [main]
12+
paths:
13+
- 'src/datamodel_code_generator/parser/schema_version.py'
14+
- 'scripts/build_schema_docs.py'
15+
- 'docs/supported_formats.md'
16+
pull_request_target:
17+
types: [labeled]
18+
paths:
19+
- 'src/datamodel_code_generator/parser/schema_version.py'
20+
- 'scripts/build_schema_docs.py'
21+
- 'docs/supported_formats.md'
22+
23+
permissions:
24+
contents: write
25+
26+
jobs:
27+
update-schema-docs:
28+
if: |
29+
github.event_name == 'push' ||
30+
!github.event.pull_request.head.repo.fork ||
31+
github.actor == 'koxudaxi' ||
32+
github.actor == 'gaborbernat' ||
33+
github.actor == 'ilovelinux' ||
34+
(github.event_name == 'pull_request_target' && github.event.label.name == 'safe-to-fix' &&
35+
(github.event.sender.login == 'koxudaxi' ||
36+
github.event.sender.login == 'gaborbernat' ||
37+
github.event.sender.login == 'ilovelinux'))
38+
runs-on: ubuntu-latest
39+
steps:
40+
# Checkout for forks (no PAT available)
41+
- uses: actions/checkout@v4
42+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
43+
with:
44+
fetch-depth: 0
45+
ref: ${{ github.event.pull_request.head.ref }}
46+
repository: ${{ github.event.pull_request.head.repo.full_name }}
47+
# Checkout for same-repo PRs, pushes, and pull_request_target
48+
- uses: actions/checkout@v4
49+
if: github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository
50+
with:
51+
fetch-depth: 0
52+
ref: ${{ github.event.pull_request.head.ref || github.ref }}
53+
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
54+
token: ${{ secrets.PAT }}
55+
- name: Install the latest version of uv
56+
uses: astral-sh/setup-uv@v5
57+
- name: Install tox
58+
run: uv tool install --python-preference only-managed --python 3.13 tox --with tox-uv
59+
- name: Setup environment
60+
run: tox run -vv --notest --skip-missing-interpreters false -e schema-docs
61+
env:
62+
UV_PYTHON_PREFERENCE: "only-managed"
63+
- name: Build schema docs
64+
run: .tox/schema-docs/bin/python scripts/build_schema_docs.py
65+
- name: Commit and push if changed
66+
if: github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository
67+
run: |
68+
git config user.name "github-actions[bot]"
69+
git config user.email "github-actions[bot]@users.noreply.github.com"
70+
git add docs/supported_formats.md
71+
if ! git diff --staged --quiet; then
72+
git commit -m "docs: update schema feature documentation
73+
74+
🤖 Generated by GitHub Actions"
75+
git push
76+
fi

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ repos:
4343
language: system
4444
files: ^src/datamodel_code_generator/config\.py$
4545
pass_filenames: false
46+
- id: schema-docs
47+
name: Update schema feature documentation
48+
entry: bash -c '[ -x .tox/schema-docs/bin/python ] && PYTHON=.tox/schema-docs/bin/python || PYTHON=.tox/schema-docs/Scripts/python; test -x "$PYTHON" || tox run -e schema-docs --notest -qq; "$PYTHON" scripts/build_schema_docs.py'
49+
language: system
50+
files: ^(src/datamodel_code_generator/parser/schema_version\.py|scripts/build_schema_docs\.py|docs/supported_formats\.md)$
51+
pass_filenames: false

docs/llms-full.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24144,6 +24144,32 @@ datamodel-code-generator detects the OpenAPI version from the `openapi` field:
2414424144
- `openapi: "3.1.x"` -> OpenAPI 3.1
2414524145
- No `openapi` field -> Fallback to OpenAPI 3.1
2414624146

24147+
<!-- BEGIN AUTO-GENERATED SUPPORTED FEATURES -->
24148+
### Supported Features (from code)
24149+
24150+
The following features are tracked in the codebase with their implementation status:
24151+
24152+
#### JSON Schema Features
24153+
24154+
| Feature | Introduced | Status | Description |
24155+
|---------|------------|--------|-------------|
24156+
| `Null in type array` | 2020-12 | ✅ Supported | Allows `type: ['string', 'null']` syntax for nullable types |
24157+
| `$defs` | 2019-09 | ✅ Supported | Uses `$defs` instead of `definitions` for schema definitions |
24158+
| `prefixItems` | 2020-12 | ✅ Supported | Tuple validation using `prefixItems` keyword |
24159+
| `Boolean schemas` | Draft 6 | ✅ Supported | Allows `true` and `false` as valid schemas |
24160+
| `$id` | Draft 6 | ✅ Supported | Schema identifier field (`id` in Draft 4, `$id` in Draft 6+) |
24161+
| `definitions/$defs` | Draft 4 | ✅ Supported | Key for reusable schema definitions |
24162+
| `exclusiveMinimum/Maximum as number` | Draft 6 | ✅ Supported | Numeric `exclusiveMinimum`/`exclusiveMaximum` (boolean in Draft 4) |
24163+
| `readOnly/writeOnly` | Draft 7 | ✅ Supported | Field visibility hints for read-only and write-only properties |
24164+
24165+
#### OpenAPI-Specific Features
24166+
24167+
| Feature | Introduced | Status | Description |
24168+
|---------|------------|--------|-------------|
24169+
| `nullable` | OAS 3.0 | ✅ Supported | Uses `nullable: true` for nullable types (deprecated in 3.1) |
24170+
| `discriminator` | OAS 3.0 | ✅ Supported | Polymorphism support via `discriminator` keyword |
24171+
<!-- END AUTO-GENERATED SUPPORTED FEATURES -->
24172+
2414724173
## Data Format Support
2414824174

2414924175
### Common Formats (JSON Schema + OpenAPI)

docs/supported_formats.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,32 @@ datamodel-code-generator detects the OpenAPI version from the `openapi` field:
9090
- `openapi: "3.1.x"` -> OpenAPI 3.1
9191
- No `openapi` field -> Fallback to OpenAPI 3.1
9292

93+
<!-- BEGIN AUTO-GENERATED SUPPORTED FEATURES -->
94+
### Supported Features (from code)
95+
96+
The following features are tracked in the codebase with their implementation status:
97+
98+
#### JSON Schema Features
99+
100+
| Feature | Introduced | Status | Description |
101+
|---------|------------|--------|-------------|
102+
| `Null in type array` | 2020-12 | ✅ Supported | Allows `type: ['string', 'null']` syntax for nullable types |
103+
| `$defs` | 2019-09 | ✅ Supported | Uses `$defs` instead of `definitions` for schema definitions |
104+
| `prefixItems` | 2020-12 | ✅ Supported | Tuple validation using `prefixItems` keyword |
105+
| `Boolean schemas` | Draft 6 | ✅ Supported | Allows `true` and `false` as valid schemas |
106+
| `$id` | Draft 6 | ✅ Supported | Schema identifier field (`id` in Draft 4, `$id` in Draft 6+) |
107+
| `definitions/$defs` | Draft 4 | ✅ Supported | Key for reusable schema definitions |
108+
| `exclusiveMinimum/Maximum as number` | Draft 6 | ✅ Supported | Numeric `exclusiveMinimum`/`exclusiveMaximum` (boolean in Draft 4) |
109+
| `readOnly/writeOnly` | Draft 7 | ✅ Supported | Field visibility hints for read-only and write-only properties |
110+
111+
#### OpenAPI-Specific Features
112+
113+
| Feature | Introduced | Status | Description |
114+
|---------|------------|--------|-------------|
115+
| `nullable` | OAS 3.0 | ✅ Supported | Uses `nullable: true` for nullable types (deprecated in 3.1) |
116+
| `discriminator` | OAS 3.0 | ✅ Supported | Polymorphism support via `discriminator` keyword |
117+
<!-- END AUTO-GENERATED SUPPORTED FEATURES -->
118+
93119
## Data Format Support
94120

95121
### Common Formats (JSON Schema + OpenAPI)

scripts/build_schema_docs.py

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from __future__ import annotations
1212

1313
import argparse
14+
import re
1415
import sys
1516
from dataclasses import fields
1617
from pathlib import Path
@@ -26,6 +27,10 @@
2627

2728
DOCS_PATH = Path(__file__).parent.parent / "docs" / "supported_formats.md"
2829

30+
# Markers for auto-generated content
31+
BEGIN_MARKER = "<!-- BEGIN AUTO-GENERATED SUPPORTED FEATURES -->"
32+
END_MARKER = "<!-- END AUTO-GENERATED SUPPORTED FEATURES -->"
33+
2934
# Status emoji mapping
3035
STATUS_EMOJI = {
3136
"supported": "✅",
@@ -114,10 +119,9 @@ def print_features_summary() -> None:
114119
print()
115120

116121

117-
def generate_supported_features_table() -> str:
118-
"""Generate the supported features table for documentation."""
122+
def generate_supported_features_content() -> str:
123+
"""Generate the supported features content for documentation (without markers)."""
119124
lines = [
120-
"<!-- BEGIN AUTO-GENERATED SUPPORTED FEATURES -->",
121125
"",
122126
"### Supported Features (from code)",
123127
"",
@@ -136,11 +140,68 @@ def generate_supported_features_table() -> str:
136140
openapi_features = [
137141
(name, meta) for name, meta in get_feature_metadata(OpenAPISchemaFeatures) if name not in json_field_names
138142
]
139-
lines.extend((generate_feature_table(openapi_features), "", "<!-- END AUTO-GENERATED SUPPORTED FEATURES -->"))
143+
lines.extend((generate_feature_table(openapi_features), ""))
140144

141145
return "\n".join(lines)
142146

143147

148+
def generate_supported_features_table() -> str:
149+
"""Generate the supported features table with markers for documentation."""
150+
return f"{BEGIN_MARKER}{generate_supported_features_content()}{END_MARKER}"
151+
152+
153+
def update_docs_file(*, check: bool = False) -> int:
154+
"""Update docs/supported_formats.md with auto-generated content.
155+
156+
Args:
157+
check: If True, only check if content would change (returns 1 if different).
158+
159+
Returns:
160+
0 on success, 1 on failure or if check mode detects changes.
161+
"""
162+
if not DOCS_PATH.exists():
163+
print(f"Error: {DOCS_PATH} does not exist", file=sys.stderr)
164+
return 1
165+
166+
current_content = DOCS_PATH.read_text(encoding="utf-8")
167+
new_generated = generate_supported_features_content()
168+
169+
# Pattern to match content between markers (including markers)
170+
pattern = re.compile(
171+
rf"{re.escape(BEGIN_MARKER)}.*?{re.escape(END_MARKER)}",
172+
re.DOTALL,
173+
)
174+
175+
if pattern.search(current_content):
176+
# Replace existing auto-generated section
177+
new_content = pattern.sub(
178+
f"{BEGIN_MARKER}{new_generated}{END_MARKER}",
179+
current_content,
180+
)
181+
else:
182+
print(f"Warning: Markers not found in {DOCS_PATH}", file=sys.stderr)
183+
print("Please add the following markers to the file:", file=sys.stderr)
184+
print(f" {BEGIN_MARKER}", file=sys.stderr)
185+
print(f" {END_MARKER}", file=sys.stderr)
186+
return 1
187+
188+
if check:
189+
if current_content != new_content:
190+
print(f"Schema docs are out of date: {DOCS_PATH}", file=sys.stderr)
191+
print("Run 'python scripts/build_schema_docs.py' to update.", file=sys.stderr)
192+
return 1
193+
print("Schema docs are up to date.")
194+
return 0
195+
196+
if current_content == new_content:
197+
print("Schema docs are already up to date.")
198+
return 0
199+
200+
DOCS_PATH.write_text(new_content, encoding="utf-8")
201+
print(f"Updated {DOCS_PATH}")
202+
return 0
203+
204+
144205
def main() -> int:
145206
"""Parse arguments and build documentation."""
146207
parser = argparse.ArgumentParser(description="Build schema documentation from code metadata")
@@ -160,21 +221,7 @@ def main() -> int:
160221
print_features_summary()
161222
return 0
162223

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
224+
return update_docs_file(check=args.check)
178225

179226

180227
if __name__ == "__main__":

tox.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ commands =
111111
check-wheel-contents --no-config {env_tmp_dir}
112112
dependency_groups = pkg-meta
113113

114+
[testenv:schema-docs]
115+
description = Build schema feature documentation (use --check to validate only)
116+
commands =
117+
python scripts/build_schema_docs.py {posargs}
118+
dependency_groups =
119+
no_default_groups = true
120+
114121
[testenv:llms-txt]
115122
description = Generate llms.txt files (use --check to validate only, requires Python 3.11+)
116123
commands =

0 commit comments

Comments
 (0)