@@ -275,6 +275,148 @@ def test_returns_false(self, schema: dict[str, Any]) -> None:
275275 assert has_underscore_fields (schema ) is False
276276
277277
278+ class TestCreateModelJsonSchemaRoundtrip :
279+ """Verify that model_json_schema() works on models produced by create_model().
280+
281+ Pydantic re-resolves forward references via sys.modules[cls.__module__],
282+ so all types must be registered in the pseudo-module.
283+ """
284+
285+ def test_single_ref (self ) -> None :
286+ schema : dict [str , Any ] = {
287+ "type" : "object" ,
288+ "properties" : {
289+ "address" : {"$ref" : "#/$defs/Address" },
290+ },
291+ "$defs" : {
292+ "Address" : {
293+ "type" : "object" ,
294+ "properties" : {
295+ "street" : {"type" : "string" },
296+ "city" : {"type" : "string" },
297+ },
298+ },
299+ },
300+ }
301+ model = create_model (schema )
302+ result = model .model_json_schema ()
303+ defs = result .get ("$defs" ) or result .get ("definitions" ) or {}
304+ assert len (defs ) == 1
305+
306+ def test_deeply_nested_defs_chain (self ) -> None :
307+ """Type B is only referenced by type A (not by root). Old code missed B."""
308+ schema : dict [str , Any ] = {
309+ "type" : "object" ,
310+ "properties" : {
311+ "order" : {"$ref" : "#/$defs/Order" },
312+ },
313+ "$defs" : {
314+ "Order" : {
315+ "type" : "object" ,
316+ "properties" : {
317+ "item" : {"$ref" : "#/$defs/Item" },
318+ "quantity" : {"type" : "integer" },
319+ },
320+ },
321+ "Item" : {
322+ "type" : "object" ,
323+ "properties" : {
324+ "name" : {"type" : "string" },
325+ "price" : {"type" : "number" },
326+ },
327+ },
328+ },
329+ }
330+ model = create_model (schema )
331+ result = model .model_json_schema ()
332+ defs = result .get ("$defs" ) or result .get ("definitions" ) or {}
333+ assert len (defs ) == 2
334+
335+ def test_three_level_defs_chain (self ) -> None :
336+ """Root -> A -> B -> C. C is two levels removed from root fields."""
337+ schema : dict [str , Any ] = {
338+ "type" : "object" ,
339+ "properties" : {
340+ "company" : {"$ref" : "#/$defs/Company" },
341+ },
342+ "$defs" : {
343+ "Company" : {
344+ "type" : "object" ,
345+ "properties" : {
346+ "department" : {"$ref" : "#/$defs/Department" },
347+ },
348+ },
349+ "Department" : {
350+ "type" : "object" ,
351+ "properties" : {
352+ "manager" : {"$ref" : "#/$defs/Employee" },
353+ },
354+ },
355+ "Employee" : {
356+ "type" : "object" ,
357+ "properties" : {
358+ "name" : {"type" : "string" },
359+ "role" : {"type" : "string" },
360+ },
361+ },
362+ },
363+ }
364+ model = create_model (schema )
365+ result = model .model_json_schema ()
366+ defs = result .get ("$defs" ) or result .get ("definitions" ) or {}
367+ assert len (defs ) == 3
368+
369+ def test_enum_in_defs (self ) -> None :
370+ """Non-BaseModel types (enums) in $defs must also be registered."""
371+ schema : dict [str , Any ] = {
372+ "type" : "object" ,
373+ "properties" : {
374+ "status" : {"$ref" : "#/$defs/Status" },
375+ },
376+ "$defs" : {
377+ "Status" : {
378+ "type" : "string" ,
379+ "enum" : ["active" , "inactive" , "pending" ],
380+ },
381+ },
382+ }
383+ model = create_model (schema )
384+ result = model .model_json_schema ()
385+ assert result is not None
386+
387+ def test_array_of_nested_refs (self ) -> None :
388+ """Array field referencing a $def type that itself has a $ref."""
389+ schema : dict [str , Any ] = {
390+ "type" : "object" ,
391+ "properties" : {
392+ "tasks" : {
393+ "type" : "array" ,
394+ "items" : {"$ref" : "#/$defs/Task" },
395+ },
396+ },
397+ "$defs" : {
398+ "Task" : {
399+ "type" : "object" ,
400+ "properties" : {
401+ "assignee" : {"$ref" : "#/$defs/Person" },
402+ "title" : {"type" : "string" },
403+ },
404+ },
405+ "Person" : {
406+ "type" : "object" ,
407+ "properties" : {
408+ "name" : {"type" : "string" },
409+ "email" : {"type" : "string" },
410+ },
411+ },
412+ },
413+ }
414+ model = create_model (schema )
415+ result = model .model_json_schema ()
416+ defs = result .get ("$defs" ) or result .get ("definitions" ) or {}
417+ assert len (defs ) == 2
418+
419+
278420class TestCreateModelRejectsUnderscoreFields :
279421 def test_top_level_underscore_field (self ) -> None :
280422 schema = {
0 commit comments