2323PYTHON_GENERATED_HEADER_PATTERN = re .compile (
2424 r"\A# generated by datamodel-codegen:\n"
2525 r"# filename: .+\n"
26- r"(?:# timestamp:\s+.+\n )?"
27- r"\n" ,
26+ r"(?:# timestamp:\s+.+(?:\n|$) )?"
27+ r"\n? " ,
2828)
2929
3030
@@ -63,10 +63,15 @@ def render_file_example(*, language: str, path: Path, strip_python_header: bool
6363 return fenced (language , content )
6464
6565
66+ def directory_files (path : Path ) -> list [Path ]:
67+ """Return stable file entries for generated directory examples."""
68+ return sorted (file for file in path .iterdir () if file .is_file ())
69+
70+
6671def directory_tree (path : Path , * , root_name : str ) -> str :
6772 """Render a stable text tree for generated directory examples."""
6873 lines = [f"{ root_name } /" ]
69- files = sorted ( file for file in path . iterdir () if file . is_file () )
74+ files = directory_files ( path )
7075 for index , file in enumerate (files ):
7176 branch = "`--" if index == len (files ) - 1 else "|--"
7277 lines .append (f"{ branch } { file .name } " )
@@ -81,14 +86,35 @@ def render_reuse_scope_tree_example() -> str:
8186 "**Output with `--reuse-scope tree`:**" ,
8287 fenced ("text" , directory_tree (output_path , root_name = "models" )),
8388 ]
84- for file_name in ("schema_a.py" , "schema_b.py" , "shared.py" ):
89+ for file in directory_files (output_path ):
90+ content = read_python_output (file )
8591 lines .extend ((
86- f"**models/{ file_name } :**" ,
87- fenced ("python" , read_python_output ( output_path / file_name )) ,
92+ f"**models/{ file . name } :**" ,
93+ fenced ("python" , content ) if content . strip () else "_No code beyond the generated header._ \n " ,
8894 ))
8995 return "\n " .join (lines )
9096
9197
98+ def _file_example (
99+ example_id : str ,
100+ doc : str ,
101+ * ,
102+ language : str ,
103+ path : Path ,
104+ strip_python_header : bool = False ,
105+ ) -> DocsExample :
106+ """Build a docs example from one fixture file."""
107+ return DocsExample (
108+ example_id = example_id ,
109+ path = DOCS / doc ,
110+ render = lambda : render_file_example (
111+ language = language ,
112+ path = path ,
113+ strip_python_header = strip_python_header ,
114+ ),
115+ )
116+
117+
92118def docs_examples () -> tuple [DocsExample , ...]:
93119 """Return all generated docs example sections."""
94120 return (
@@ -97,70 +123,56 @@ def docs_examples() -> tuple[DocsExample, ...]:
97123 path = DOCS / "openapi.md" ,
98124 render = lambda : details ("api.yaml" , "yaml" , read_text (TEST_DATA / "openapi" / "api.yaml" )),
99125 ),
100- DocsExample (
101- example_id = "openapi.quick-start.output" ,
102- path = DOCS / "openapi.md" ,
103- render = lambda : render_file_example (
104- language = "python" ,
105- path = EXPECTED_MAIN / "openapi" / "general.py" ,
106- strip_python_header = True ,
107- ),
126+ _file_example (
127+ "openapi.quick-start.output" ,
128+ "openapi.md" ,
129+ language = "python" ,
130+ path = EXPECTED_MAIN / "openapi" / "general.py" ,
131+ strip_python_header = True ,
108132 ),
109- DocsExample (
110- example_id = "openapi.read-only-write-only.schema" ,
111- path = DOCS / "openapi.md" ,
112- render = lambda : render_file_example (
113- language = "yaml" ,
114- path = TEST_DATA / "openapi" / "read_only_write_only.yaml" ,
115- ),
133+ _file_example (
134+ "openapi.read-only-write-only.schema" ,
135+ "openapi.md" ,
136+ language = "yaml" ,
137+ path = TEST_DATA / "openapi" / "read_only_write_only.yaml" ,
116138 ),
117- DocsExample (
118- example_id = "openapi.read-only-write-only.output" ,
119- path = DOCS / "openapi.md" ,
120- render = lambda : render_file_example (
121- language = "python" ,
122- path = EXPECTED_MAIN / "openapi" / "read_only_write_only_all.py" ,
123- strip_python_header = True ,
124- ),
139+ _file_example (
140+ "openapi.read-only-write-only.output" ,
141+ "openapi.md" ,
142+ language = "python" ,
143+ path = EXPECTED_MAIN / "openapi" / "read_only_write_only_all.py" ,
144+ strip_python_header = True ,
125145 ),
126- DocsExample (
127- example_id = "openapi.read-only-write-only-allof.schema" ,
128- path = DOCS / "openapi.md" ,
129- render = lambda : render_file_example (
130- language = "yaml" ,
131- path = TEST_DATA / "openapi" / "read_only_write_only_allof.yaml" ,
132- ),
146+ _file_example (
147+ "openapi.read-only-write-only-allof.schema" ,
148+ "openapi.md" ,
149+ language = "yaml" ,
150+ path = TEST_DATA / "openapi" / "read_only_write_only_allof.yaml" ,
133151 ),
134- DocsExample (
135- example_id = "model-reuse.reuse-model.output" ,
136- path = DOCS / "model-reuse.md" ,
137- render = lambda : render_file_example (
138- language = "python" ,
139- path = EXPECTED_MAIN / "jsonschema" / "json_reuse_enum.py" ,
140- strip_python_header = True ,
141- ),
152+ _file_example (
153+ "model-reuse.reuse-model.output" ,
154+ "model-reuse.md" ,
155+ language = "python" ,
156+ path = EXPECTED_MAIN / "jsonschema" / "json_reuse_enum.py" ,
157+ strip_python_header = True ,
142158 ),
143159 DocsExample (
144160 example_id = "model-reuse.reuse-scope-tree.output" ,
145161 path = DOCS / "model-reuse.md" ,
146162 render = render_reuse_scope_tree_example ,
147163 ),
148- DocsExample (
149- example_id = "model-reuse.use-type-alias.schema" ,
150- path = DOCS / "model-reuse.md" ,
151- render = lambda : render_file_example (
152- language = "json" ,
153- path = TEST_DATA / "jsonschema" / "reduce_duplicate_field_types.json" ,
154- ),
164+ _file_example (
165+ "model-reuse.use-type-alias.schema" ,
166+ "model-reuse.md" ,
167+ language = "json" ,
168+ path = TEST_DATA / "jsonschema" / "reduce_duplicate_field_types.json" ,
155169 ),
156- DocsExample (
157- example_id = "model-reuse.use-type-alias.output" ,
158- path = DOCS / "model-reuse.md" ,
159- render = lambda : render_file_example (
160- language = "python" ,
161- path = EXPECTED_MAIN / "jsonschema" / "reduce_duplicate_field_types.py" ,
162- strip_python_header = True ,
163- ),
170+ _file_example (
171+ "model-reuse.use-type-alias.output" ,
172+ "model-reuse.md" ,
173+ language = "python" ,
174+ path = EXPECTED_MAIN / "jsonschema" / "reduce_duplicate_field_types.py" ,
175+ strip_python_header = True ,
164176 ),
165177 )
166178
@@ -189,25 +201,37 @@ def iter_examples_by_path() -> Iterable[tuple[Path, tuple[DocsExample, ...]]]:
189201
190202def update_docs_examples (* , check : bool = False ) -> int :
191203 """Update or check generated documentation examples."""
192- changed_paths = []
204+ changed_content_by_path = {}
205+ out_of_date_paths = []
206+ missing_markers = False
193207 for path , examples in iter_examples_by_path ():
194208 content = path .read_text (encoding = "utf-8" )
195209 updated_content = content
210+ missing_marker_for_path = False
196211 for example in examples :
197212 updated_content , found = replace_example (updated_content , example )
198213 if not found :
199- return 1
214+ missing_markers = True
215+ missing_marker_for_path = True
216+ if missing_marker_for_path :
217+ continue
200218 if updated_content == content :
201219 continue
202220 if check :
203- print (f"Docs examples are out of date: { path } " , file = sys .stderr )
204- print ("Run 'python scripts/build_docs_examples.py' to update." , file = sys .stderr )
205- return 1
206- path .write_text (updated_content , encoding = "utf-8" )
207- changed_paths .append (path )
208-
209- if changed_paths :
210- for path in changed_paths :
221+ out_of_date_paths .append (path )
222+ continue
223+ changed_content_by_path [path ] = updated_content
224+
225+ for path in out_of_date_paths :
226+ print (f"Docs examples are out of date: { path } " , file = sys .stderr )
227+ if out_of_date_paths :
228+ print ("Run 'python scripts/build_docs_examples.py' to update." , file = sys .stderr )
229+ if missing_markers or out_of_date_paths :
230+ return 1
231+
232+ if changed_content_by_path :
233+ for path , updated_content in changed_content_by_path .items ():
234+ path .write_text (updated_content , encoding = "utf-8" )
211235 print (f"Updated { path } " )
212236 return 0
213237
0 commit comments