@@ -5443,15 +5443,25 @@ def _handle_python_import(
54435443 def _is_named_schema_definition_path (self , path : list [str ]) -> bool :
54445444 """Check if path points to a named schema entry under definitions/$defs."""
54455445 current_root = list (self .model_resolver .current_root )
5446- expected_path_length = len (current_root ) + 2
5447- if len (path ) != expected_path_length :
5446+ if len (path ) < len (current_root ) + 2 :
54485447 return False
54495448
54505449 schema_container_path = path [len (current_root )]
54515450 return path [: len (current_root )] == current_root and any (
54525451 schema_container_path == schema_path for schema_path , _ in self .schema_paths
54535452 )
54545453
5454+ def _is_current_root_schema_path (self , path : list [str ]) -> bool :
5455+ current_root = list (self .model_resolver .current_root )
5456+ if path == (current_root or ["#" ]):
5457+ return True
5458+ return self .model_resolver .resolve_ref (path ) == self .model_resolver .resolve_ref (current_root or "#" )
5459+
5460+ def _drop_ref_from_schema (self , obj : JsonSchemaObject ) -> JsonSchemaObject :
5461+ return self .SCHEMA_OBJECT_TYPE .model_validate (
5462+ obj .model_dump (exclude = {"ref" }, exclude_unset = True , by_alias = True )
5463+ )
5464+
54555465 def parse_obj ( # noqa: PLR0912
54565466 self ,
54575467 name : str ,
@@ -5460,7 +5470,10 @@ def parse_obj( # noqa: PLR0912
54605470 ) -> None :
54615471 """Parse a JsonSchemaObject by dispatching to appropriate parse methods."""
54625472 if obj .has_ref_with_schema_keywords and not obj .is_ref_with_nullable_only :
5463- obj = self ._merge_ref_with_schema (obj )
5473+ if obj .ref == "#" and self ._is_current_root_schema_path (path ):
5474+ obj = self ._drop_ref_from_schema (obj )
5475+ else :
5476+ obj = self ._merge_ref_with_schema (obj )
54645477 if obj .ref :
54655478 if self ._is_named_schema_definition_path (path ):
54665479 self .parse_root_type (name , obj , path )
@@ -5646,6 +5659,78 @@ def parse_json_pointer(self, raw: dict[str, YamlValue], ref: str, path_parts: li
56465659
56475660 self .parse_raw_obj (model_name , models , [* path_parts , f"#/{ reference_paths [0 ]} " , * reference_paths [1 :]])
56485661
5662+ def _known_schema_object_raw_keys (self ) -> set [str ]:
5663+ keys = {"definitions" , "$defs" }
5664+ for name , field in self .SCHEMA_OBJECT_TYPE .get_fields ().items (): # ty: ignore
5665+ keys .add (name )
5666+ if alias := getattr (field , "alias" , None ):
5667+ keys .add (alias )
5668+ return keys
5669+
5670+ def _has_schema_affecting_keywords (self , raw : dict [str , Any ]) -> bool :
5671+ metadata_keys = {
5672+ * self .SCHEMA_OBJECT_TYPE .__metadata_only_fields__ ,
5673+ "extras" ,
5674+ self .SCHEMA_OBJECT_TYPE .__extra_key__ ,
5675+ }
5676+ schema_affecting_keys = {
5677+ * self ._known_schema_object_raw_keys (),
5678+ * self .SCHEMA_OBJECT_TYPE .__schema_affecting_extras__ ,
5679+ } - metadata_keys
5680+ return any (str (key ) in schema_affecting_keys for key in raw )
5681+
5682+ def _is_version_definition_namespace_name (self , name : str ) -> bool : # noqa: PLR6301
5683+ return re .fullmatch (r"v\d+(?:[._-]\d+)*" , name , flags = re .IGNORECASE ) is not None
5684+
5685+ def _iter_definition_namespace_entries (
5686+ self ,
5687+ raw : dict [str , Any ],
5688+ path : list [str ],
5689+ * ,
5690+ include_direct_children : bool ,
5691+ ) -> Iterator [tuple [str , YamlValue , list [str ]]]:
5692+ for schema_key in ("definitions" , "$defs" ):
5693+ if isinstance (definitions := raw .get (schema_key ), dict ):
5694+ yield from self ._iter_schema_definition_entries (definitions , [* path , schema_key ])
5695+
5696+ if not include_direct_children :
5697+ return
5698+
5699+ known_keys = self ._known_schema_object_raw_keys ()
5700+ for key , value in raw .items ():
5701+ key_str = str (key )
5702+ if key_str in known_keys or key_str .startswith ("x-" ) or not isinstance (value , (dict , bool )):
5703+ continue
5704+ yield from self ._iter_schema_definition_entry (key_str , value , [* path , key_str ])
5705+
5706+ def _iter_schema_definition_entry (
5707+ self ,
5708+ name : str ,
5709+ raw : YamlValue ,
5710+ path : list [str ],
5711+ ) -> Iterator [tuple [str , YamlValue , list [str ]]]:
5712+ if isinstance (raw , dict ) and not self ._has_schema_affecting_keywords (raw ):
5713+ entries = list (
5714+ self ._iter_definition_namespace_entries (
5715+ raw ,
5716+ path ,
5717+ include_direct_children = self ._is_version_definition_namespace_name (name ),
5718+ )
5719+ )
5720+ if entries :
5721+ yield from entries
5722+ return
5723+ yield name , raw , path
5724+
5725+ def _iter_schema_definition_entries (
5726+ self ,
5727+ definitions : dict [str , YamlValue ],
5728+ base_path : list [str ],
5729+ ) -> Iterator [tuple [str , YamlValue , list [str ]]]:
5730+ for key , model in definitions .items ():
5731+ name = str (key )
5732+ yield from self ._iter_schema_definition_entry (name , model , [* base_path , name ])
5733+
56495734 def _parse_file ( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
56505735 self ,
56515736 raw : dict [str , Any ],
@@ -5697,8 +5782,16 @@ def _parse_file( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
56975782 except KeyError : # pragma: no cover
56985783 continue
56995784
5700- for key , model in definitions .items ():
5701- definition_path = [* path_parts , schema_path , key ]
5785+ definition_entries = list (self ._iter_schema_definition_entries (definitions , [* path_parts , schema_path ]))
5786+ definition_metadata_entries = [
5787+ * ((str (key ), model , [* path_parts , schema_path , str (key )]) for key , model in definitions .items ()),
5788+ * definition_entries ,
5789+ ]
5790+ seen_definition_metadata_paths : set [tuple [str , ...]] = set ()
5791+ for _key , model , definition_path in definition_metadata_entries :
5792+ if (definition_path_key := tuple (definition_path )) in seen_definition_metadata_paths :
5793+ continue
5794+ seen_definition_metadata_paths .add (definition_path_key )
57025795 obj = self ._validate_schema_object (model , definition_path )
57035796 self .parse_id (obj , definition_path )
57045797 if obj .recursiveAnchor :
@@ -5714,8 +5807,7 @@ def _parse_file( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
57145807 self .parse_obj (model_name , self ._validate_schema_object (models , path ), path )
57155808 elif not self .skip_root_model :
57165809 self .parse_obj (obj_name , root_obj , path_parts or ["#" ])
5717- for key , model in definitions .items ():
5718- path = [* path_parts , schema_path , key ]
5810+ for key , model , path in definition_entries :
57195811 reference = self .model_resolver .get (path )
57205812 if not reference or not reference .loaded :
57215813 self .parse_raw_obj (key , model , path )
0 commit comments