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