11from typing import Any
2+ from typing import Dict
23from typing import Generic
34from typing import Iterable
45from typing import List
1112from jsonschema_path import SchemaPath
1213
1314from openapi_core .casting .schemas .exceptions import CastError
15+ from openapi_core .deserializing .media_types .enums import DataKind
1416from openapi_core .schema .schemas import get_properties
1517from openapi_core .util import forcebool
1618from openapi_core .validation .schemas .validators import SchemaValidator
@@ -27,7 +29,12 @@ def __init__(
2729 self .schema_validator = schema_validator
2830 self .schema_caster = schema_caster
2931
30- def __call__ (self , value : Any ) -> Any :
32+ def __call__ (
33+ self ,
34+ value : Any ,
35+ data_kind : DataKind = DataKind .STRINGLY ,
36+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
37+ ) -> Any :
3138 return value
3239
3340
@@ -37,16 +44,22 @@ def __call__(self, value: Any) -> Any:
3744class PrimitiveTypeCaster (Generic [PrimitiveType ], PrimitiveCaster ):
3845 primitive_type : Type [PrimitiveType ] = NotImplemented
3946
40- def __call__ (self , value : Union [str , bytes ]) -> Any :
47+ def __call__ (
48+ self ,
49+ value : Union [str , bytes ],
50+ data_kind : DataKind = DataKind .STRINGLY ,
51+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
52+ ) -> Any :
53+ # STRINGLY: only coerce from str/bytes, return others unchanged
54+ if data_kind == DataKind .STRINGLY :
55+ if not isinstance (value , (str , bytes )):
56+ return value
57+
4158 self .validate (value )
4259
4360 return self .primitive_type (value ) # type: ignore [call-arg]
4461
4562 def validate (self , value : Any ) -> None :
46- # FIXME: don't cast data from media type deserializer
47- # See https://github.com/python-openapi/openapi-core/issues/706
48- # if not isinstance(value, (str, bytes)):
49- # raise ValueError("should cast only from string or bytes")
5063 pass
5164
5265
@@ -61,19 +74,29 @@ class NumberCaster(PrimitiveTypeCaster[float]):
6174class BooleanCaster (PrimitiveTypeCaster [bool ]):
6275 primitive_type = bool
6376
64- def __call__ (self , value : Union [str , bytes ]) -> Any :
77+ def __call__ (
78+ self ,
79+ value : Union [str , bytes ],
80+ data_kind : DataKind = DataKind .STRINGLY ,
81+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
82+ ) -> Any :
83+ # STRINGLY: only coerce from str/bytes, return others unchanged
84+ if data_kind == DataKind .STRINGLY :
85+ if not isinstance (value , (str , bytes )):
86+ return value
87+
6588 self .validate (value )
6689
6790 return self .primitive_type (forcebool (value ))
6891
6992 def validate (self , value : Any ) -> None :
70- super ().validate (value )
71-
72- # FIXME: don't cast data from media type deserializer
73- # See https://github.com/python-openapi/openapi-core/issues/706
93+ # Already a boolean, accept it
7494 if isinstance (value , bool ):
7595 return
7696
97+ if not isinstance (value , (str , bytes )):
98+ return
99+
77100 if value .lower () not in ["false" , "true" ]:
78101 raise ValueError ("not a boolean format" )
79102
@@ -85,20 +108,34 @@ def items_caster(self) -> "SchemaCaster":
85108 items_schema = self .schema .get ("items" , SchemaPath .from_dict ({}))
86109 return self .schema_caster .evolve (items_schema )
87110
88- def __call__ (self , value : Any ) -> List [Any ]:
111+ def __call__ (
112+ self ,
113+ value : Any ,
114+ data_kind : DataKind = DataKind .STRINGLY ,
115+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
116+ ) -> List [Any ]:
89117 # str and bytes are not arrays according to the OpenAPI spec
90118 if isinstance (value , (str , bytes )) or not isinstance (value , Iterable ):
91119 raise CastError (value , self .schema ["type" ])
92120
121+ # Array items don't have property names, pass empty policies dict
93122 try :
94- return list (map (self .items_caster .cast , value ))
123+ return list (
124+ map (lambda item : self .items_caster .cast (item , data_kind = data_kind , property_data_kinds = {}), value )
125+ )
95126 except (ValueError , TypeError ):
96127 raise CastError (value , self .schema ["type" ])
97128
98129
99130class ObjectCaster (PrimitiveCaster ):
100- def __call__ (self , value : Any ) -> Any :
101- return self ._cast_proparties (value )
131+ def __call__ (
132+ self ,
133+ value : Any ,
134+ data_kind : DataKind = DataKind .STRINGLY ,
135+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
136+ ) -> Any :
137+ return self ._cast_proparties (value , schema_only = False , data_kind = data_kind ,
138+ property_data_kinds = property_data_kinds or {})
102139
103140 def evolve (self , schema : SchemaPath ) -> "ObjectCaster" :
104141 cls = self .__class__
@@ -109,14 +146,24 @@ def evolve(self, schema: SchemaPath) -> "ObjectCaster":
109146 self .schema_caster .evolve (schema ),
110147 )
111148
112- def _cast_proparties (self , value : Any , schema_only : bool = False ) -> Any :
149+ def _cast_proparties (
150+ self ,
151+ value : Any ,
152+ schema_only : bool = False ,
153+ data_kind : DataKind = DataKind .STRINGLY ,
154+ property_data_kinds : Optional [Dict [str , DataKind ]] = None
155+ ) -> Any :
113156 if not isinstance (value , dict ):
114157 raise CastError (value , self .schema ["type" ])
115158
159+ if property_data_kinds is None :
160+ property_data_kinds = {}
161+
116162 all_of_schemas = self .schema_validator .iter_all_of_schemas (value )
117163 for all_of_schema in all_of_schemas :
118164 all_of_properties = self .evolve (all_of_schema )._cast_proparties (
119- value , schema_only = True
165+ value , schema_only = True , data_kind = data_kind ,
166+ property_data_kinds = property_data_kinds
120167 )
121168 value .update (all_of_properties )
122169
@@ -125,8 +172,14 @@ def _cast_proparties(self, value: Any, schema_only: bool = False) -> Any:
125172 prop_value = value [prop_name ]
126173 except KeyError :
127174 continue
175+
176+ # Use property-specific policy if available, otherwise inherit parent policy
177+ prop_data_kind = property_data_kinds .get (prop_name , data_kind )
178+
128179 value [prop_name ] = self .schema_caster .evolve (prop_schema ).cast (
129- prop_value
180+ prop_value ,
181+ data_kind = prop_data_kind ,
182+ property_data_kinds = {} # Policies don't cascade to nested objects
130183 )
131184
132185 if schema_only :
@@ -150,7 +203,7 @@ def _cast_proparties(self, value: Any, schema_only: bool = False) -> Any:
150203 for prop_name , prop_value in value .items ():
151204 if prop_name in value :
152205 continue
153- value [prop_name ] = additional_prop_caster .cast (prop_value )
206+ value [prop_name ] = additional_prop_caster .cast (prop_value , data_kind = data_kind )
154207
155208 return value
156209
@@ -197,7 +250,16 @@ def __init__(
197250
198251 self .types_caster = types_caster
199252
200- def cast (self , value : Any ) -> Any :
253+ def cast (
254+ self ,
255+ value : Any ,
256+ data_kind : DataKind = DataKind .STRINGLY ,
257+ property_data_kinds : Optional [Dict [str , DataKind ]] = None ,
258+ ) -> Any :
259+ # TYPED data: return value unchanged, no casting applied
260+ if data_kind == DataKind .TYPED :
261+ return value
262+
201263 # skip casting for nullable in OpenAPI 3.0
202264 if value is None and self .schema .getkey ("nullable" , False ):
203265 return value
@@ -210,7 +272,7 @@ def cast(self, value: Any) -> Any:
210272 return value
211273
212274 try :
213- return type_caster (value )
275+ return type_caster (value , data_kind = data_kind , property_data_kinds = property_data_kinds )
214276 except (ValueError , TypeError ):
215277 raise CastError (value , schema_type )
216278
0 commit comments