@@ -993,12 +993,139 @@ def _should_generate_separate_models(
993993
994994 def _should_generate_base_model (self , * , generates_separate_models : bool = False ) -> bool :
995995 """Determine if Base model should be generated."""
996+ if getattr (self , "_force_base_model_generation" , False ):
997+ return True
996998 if self .read_only_write_only_model_type is None :
997999 return True
9981000 if self .read_only_write_only_model_type == ReadOnlyWriteOnlyModelType .All :
9991001 return True
10001002 return not generates_separate_models
10011003
1004+ def _ref_schema_generates_variant (self , ref_path : str , suffix : str ) -> bool :
1005+ """Check if a referenced schema will generate a specific variant (Request or Response).
1006+
1007+ For Request variant: schema must have readOnly fields AND at least one non-readOnly field.
1008+ For Response variant: schema must have writeOnly fields AND at least one non-writeOnly field.
1009+ """
1010+ try :
1011+ ref_schema = self ._load_ref_schema_object (ref_path )
1012+ except Exception : # noqa: BLE001 # pragma: no cover
1013+ return False
1014+
1015+ has_read_only = False
1016+ has_write_only = False
1017+ has_non_read_only = False
1018+ has_non_write_only = False
1019+
1020+ for prop in (ref_schema .properties or {}).values ():
1021+ if not isinstance (prop , JsonSchemaObject ): # pragma: no cover
1022+ continue
1023+ is_read_only = self ._resolve_field_flag (prop , "readOnly" )
1024+ is_write_only = self ._resolve_field_flag (prop , "writeOnly" )
1025+ if is_read_only :
1026+ has_read_only = True
1027+ else :
1028+ has_non_read_only = True
1029+ if is_write_only :
1030+ has_write_only = True
1031+ else :
1032+ has_non_write_only = True
1033+
1034+ if suffix == "Request" :
1035+ return has_read_only and has_non_read_only
1036+ if suffix == "Response" :
1037+ return has_write_only and has_non_write_only
1038+ return False # pragma: no cover
1039+
1040+ def _ref_schema_has_model (self , ref_path : str ) -> bool :
1041+ """Check if a referenced schema will have a model (base or variant) generated.
1042+
1043+ Returns False if the schema has only readOnly or only writeOnly fields in request-response mode,
1044+ which would result in no model being generated at all.
1045+ """
1046+ try :
1047+ ref_schema = self ._load_ref_schema_object (ref_path )
1048+ except Exception : # noqa: BLE001 # pragma: no cover
1049+ return True
1050+
1051+ has_read_only = False
1052+ has_write_only = False
1053+
1054+ for prop in (ref_schema .properties or {}).values ():
1055+ if not isinstance (prop , JsonSchemaObject ): # pragma: no cover
1056+ continue
1057+ is_read_only = self ._resolve_field_flag (prop , "readOnly" )
1058+ is_write_only = self ._resolve_field_flag (prop , "writeOnly" )
1059+ if is_read_only :
1060+ has_read_only = True
1061+ elif is_write_only :
1062+ has_write_only = True
1063+ else : # pragma: no cover
1064+ return True
1065+
1066+ if has_read_only and not has_write_only :
1067+ return False
1068+ return not (has_write_only and not has_read_only )
1069+
1070+ def _update_data_type_ref_for_variant (self , data_type : DataType , suffix : str ) -> None :
1071+ """Recursively update data type references to point to variant models."""
1072+ if data_type .reference :
1073+ ref_path = data_type .reference .path
1074+ if self ._ref_schema_generates_variant (ref_path , suffix ):
1075+ path_parts = ref_path .split ("/" )
1076+ base_name = path_parts [- 1 ]
1077+ variant_name = f"{ base_name } { suffix } "
1078+ unique_name = self .model_resolver .get_class_name (variant_name , unique = False ).name
1079+ path_parts [- 1 ] = unique_name
1080+ variant_ref = self .model_resolver .add (path_parts , unique_name , class_name = True , unique = False )
1081+ data_type .reference = variant_ref
1082+ elif not self ._ref_schema_has_model (ref_path ): # pragma: no branch
1083+ if not hasattr (self , "_force_base_model_refs" ):
1084+ self ._force_base_model_refs : set [str ] = set ()
1085+ self ._force_base_model_refs .add (ref_path )
1086+ for nested_dt in data_type .data_types :
1087+ self ._update_data_type_ref_for_variant (nested_dt , suffix )
1088+
1089+ def _update_field_refs_for_variant (
1090+ self , model_fields : list [DataModelFieldBase ], suffix : str
1091+ ) -> list [DataModelFieldBase ]:
1092+ """Update field references in model_fields to point to variant models.
1093+
1094+ For Request models, refs should point to Request variants.
1095+ For Response models, refs should point to Response variants.
1096+ """
1097+ if self .read_only_write_only_model_type != ReadOnlyWriteOnlyModelType .RequestResponse :
1098+ return model_fields
1099+ for field in model_fields :
1100+ if field .data_type : # pragma: no branch
1101+ self ._update_data_type_ref_for_variant (field .data_type , suffix )
1102+ return model_fields
1103+
1104+ def _generate_forced_base_models (self ) -> None :
1105+ """Generate base models for schemas that are referenced as property types but lack models."""
1106+ if not hasattr (self , "_force_base_model_refs" ):
1107+ return
1108+ if not self ._force_base_model_refs : # pragma: no cover
1109+ return
1110+
1111+ existing_model_paths = {result .path for result in self .results }
1112+
1113+ for ref_path in sorted (self ._force_base_model_refs ):
1114+ if ref_path in existing_model_paths : # pragma: no cover
1115+ continue
1116+ try :
1117+ ref_schema = self ._load_ref_schema_object (ref_path )
1118+ path_parts = ref_path .split ("/" )
1119+ schema_name = path_parts [- 1 ]
1120+
1121+ self ._force_base_model_generation = True
1122+ try :
1123+ self .parse_obj (schema_name , ref_schema , path_parts )
1124+ finally :
1125+ self ._force_base_model_generation = False
1126+ except Exception : # noqa: BLE001, S110 # pragma: no cover
1127+ pass
1128+
10021129 def _create_variant_model ( # noqa: PLR0913, PLR0917
10031130 self ,
10041131 path : list [str ],
@@ -1011,6 +1138,8 @@ def _create_variant_model( # noqa: PLR0913, PLR0917
10111138 """Create a Request or Response model variant."""
10121139 if not model_fields :
10131140 return
1141+ # Update field refs to point to variant models when in request-response mode
1142+ self ._update_field_refs_for_variant (model_fields , suffix )
10141143 variant_name = f"{ base_name } { suffix } "
10151144 unique_name = self .model_resolver .get_class_name (variant_name , unique = True ).name
10161145 model_path = [* path [:- 1 ], unique_name ]
@@ -3645,7 +3774,7 @@ def parse_raw_obj(
36453774 obj = self ._validate_schema_object (raw , path )
36463775 self .parse_obj (name , obj , path )
36473776
3648- def _check_version_specific_features (
3777+ def _check_version_specific_features ( # noqa: PLR0912
36493778 self ,
36503779 raw : dict [str , YamlValue ] | YamlValue ,
36513780 path : list [str ],
@@ -3709,6 +3838,18 @@ def _check_version_specific_features(
37093838 stacklevel = 3 ,
37103839 )
37113840
3841+ if not self .schema_features .read_only_write_only :
3842+ if raw .get ("readOnly" ) is True :
3843+ warn (
3844+ f"readOnly is not supported in this schema version (Draft 7+ only). Schema path: { '/' .join (path )} " ,
3845+ stacklevel = 3 ,
3846+ )
3847+ if raw .get ("writeOnly" ) is True :
3848+ warn (
3849+ f"writeOnly is not supported in this schema version (Draft 7+ only). Schema path: { '/' .join (path )} " ,
3850+ stacklevel = 3 ,
3851+ )
3852+
37123853 def _check_array_version_features (
37133854 self ,
37143855 obj : JsonSchemaObject ,
@@ -3846,6 +3987,7 @@ def parse_raw(self) -> None:
38463987 self ._parse_file (self .raw_obj , obj_name , path_parts )
38473988
38483989 self ._resolve_unparsed_json_pointer ()
3990+ self ._generate_forced_base_models ()
38493991
38503992 def _resolve_unparsed_json_pointer (self ) -> None :
38513993 """Resolve any remaining unparsed JSON pointer references recursively."""
0 commit comments