@@ -973,12 +973,139 @@ def _should_generate_separate_models(
973973
974974 def _should_generate_base_model (self , * , generates_separate_models : bool = False ) -> bool :
975975 """Determine if Base model should be generated."""
976+ if getattr (self , "_force_base_model_generation" , False ):
977+ return True
976978 if self .read_only_write_only_model_type is None :
977979 return True
978980 if self .read_only_write_only_model_type == ReadOnlyWriteOnlyModelType .All :
979981 return True
980982 return not generates_separate_models
981983
984+ def _ref_schema_generates_variant (self , ref_path : str , suffix : str ) -> bool :
985+ """Check if a referenced schema will generate a specific variant (Request or Response).
986+
987+ For Request variant: schema must have readOnly fields AND at least one non-readOnly field.
988+ For Response variant: schema must have writeOnly fields AND at least one non-writeOnly field.
989+ """
990+ try :
991+ ref_schema = self ._load_ref_schema_object (ref_path )
992+ except Exception : # noqa: BLE001 # pragma: no cover
993+ return False
994+
995+ has_read_only = False
996+ has_write_only = False
997+ has_non_read_only = False
998+ has_non_write_only = False
999+
1000+ for prop in (ref_schema .properties or {}).values ():
1001+ if not isinstance (prop , JsonSchemaObject ):
1002+ continue
1003+ is_read_only = self ._resolve_field_flag (prop , "readOnly" )
1004+ is_write_only = self ._resolve_field_flag (prop , "writeOnly" )
1005+ if is_read_only :
1006+ has_read_only = True
1007+ else :
1008+ has_non_read_only = True
1009+ if is_write_only :
1010+ has_write_only = True
1011+ else :
1012+ has_non_write_only = True
1013+
1014+ if suffix == "Request" :
1015+ return has_read_only and has_non_read_only
1016+ if suffix == "Response" :
1017+ return has_write_only and has_non_write_only
1018+ return False # pragma: no cover
1019+
1020+ def _ref_schema_has_model (self , ref_path : str ) -> bool :
1021+ """Check if a referenced schema will have a model (base or variant) generated.
1022+
1023+ Returns False if the schema has only readOnly or only writeOnly fields in request-response mode,
1024+ which would result in no model being generated at all.
1025+ """
1026+ try :
1027+ ref_schema = self ._load_ref_schema_object (ref_path )
1028+ except Exception : # noqa: BLE001 # pragma: no cover
1029+ return True
1030+
1031+ has_read_only = False
1032+ has_write_only = False
1033+
1034+ for prop in (ref_schema .properties or {}).values ():
1035+ if not isinstance (prop , JsonSchemaObject ):
1036+ continue
1037+ is_read_only = self ._resolve_field_flag (prop , "readOnly" )
1038+ is_write_only = self ._resolve_field_flag (prop , "writeOnly" )
1039+ if is_read_only :
1040+ has_read_only = True
1041+ elif is_write_only :
1042+ has_write_only = True
1043+ else : # pragma: no cover
1044+ return True
1045+
1046+ if has_read_only and not has_write_only :
1047+ return False
1048+ return not (has_write_only and not has_read_only )
1049+
1050+ def _update_data_type_ref_for_variant (self , data_type : DataType , suffix : str ) -> None :
1051+ """Recursively update data type references to point to variant models."""
1052+ if data_type .reference :
1053+ ref_path = data_type .reference .path
1054+ if self ._ref_schema_generates_variant (ref_path , suffix ):
1055+ path_parts = ref_path .split ("/" )
1056+ base_name = path_parts [- 1 ]
1057+ variant_name = f"{ base_name } { suffix } "
1058+ unique_name = self .model_resolver .get_class_name (variant_name , unique = False ).name
1059+ path_parts [- 1 ] = unique_name
1060+ variant_ref = self .model_resolver .add (path_parts , unique_name , class_name = True , unique = False )
1061+ data_type .reference = variant_ref
1062+ elif not self ._ref_schema_has_model (ref_path ):
1063+ if not hasattr (self , "_force_base_model_refs" ):
1064+ self ._force_base_model_refs : set [str ] = set ()
1065+ self ._force_base_model_refs .add (ref_path )
1066+ for nested_dt in data_type .data_types :
1067+ self ._update_data_type_ref_for_variant (nested_dt , suffix )
1068+
1069+ def _update_field_refs_for_variant (
1070+ self , model_fields : list [DataModelFieldBase ], suffix : str
1071+ ) -> list [DataModelFieldBase ]:
1072+ """Update field references in model_fields to point to variant models.
1073+
1074+ For Request models, refs should point to Request variants.
1075+ For Response models, refs should point to Response variants.
1076+ """
1077+ if self .read_only_write_only_model_type != ReadOnlyWriteOnlyModelType .RequestResponse :
1078+ return model_fields
1079+ for field in model_fields :
1080+ if field .data_type :
1081+ self ._update_data_type_ref_for_variant (field .data_type , suffix )
1082+ return model_fields
1083+
1084+ def _generate_forced_base_models (self ) -> None :
1085+ """Generate base models for schemas that are referenced as property types but lack models."""
1086+ if not hasattr (self , "_force_base_model_refs" ):
1087+ return
1088+ if not self ._force_base_model_refs : # pragma: no cover
1089+ return
1090+
1091+ existing_model_paths = {result .path for result in self .results }
1092+
1093+ for ref_path in sorted (self ._force_base_model_refs ):
1094+ if ref_path in existing_model_paths : # pragma: no cover
1095+ continue
1096+ try :
1097+ ref_schema = self ._load_ref_schema_object (ref_path )
1098+ path_parts = ref_path .split ("/" )
1099+ schema_name = path_parts [- 1 ]
1100+
1101+ self ._force_base_model_generation = True
1102+ try :
1103+ self .parse_obj (schema_name , ref_schema , path_parts )
1104+ finally :
1105+ self ._force_base_model_generation = False
1106+ except Exception : # noqa: BLE001, S110 # pragma: no cover
1107+ pass
1108+
9821109 def _create_variant_model ( # noqa: PLR0913, PLR0917
9831110 self ,
9841111 path : list [str ],
@@ -991,6 +1118,8 @@ def _create_variant_model( # noqa: PLR0913, PLR0917
9911118 """Create a Request or Response model variant."""
9921119 if not model_fields :
9931120 return
1121+ # Update field refs to point to variant models when in request-response mode
1122+ self ._update_field_refs_for_variant (model_fields , suffix )
9941123 variant_name = f"{ base_name } { suffix } "
9951124 unique_name = self .model_resolver .get_class_name (variant_name , unique = True ).name
9961125 model_path = [* path [:- 1 ], unique_name ]
@@ -3720,6 +3849,7 @@ def parse_raw(self) -> None:
37203849 self ._parse_file (self .raw_obj , obj_name , path_parts )
37213850
37223851 self ._resolve_unparsed_json_pointer ()
3852+ self ._generate_forced_base_models ()
37233853
37243854 def _resolve_unparsed_json_pointer (self ) -> None :
37253855 """Resolve any remaining unparsed JSON pointer references recursively."""
0 commit comments