11"""OpenAPI core schemas models module"""
2+ import attr
3+ import functools
24import logging
35from collections import defaultdict
46from datetime import date , datetime
2325
2426log = logging .getLogger (__name__ )
2527
28+ @attr .s
29+ class StringFormat (object ):
30+ format = attr .ib ()
31+ validate = attr .ib ()
32+
2633
2734class Schema (object ):
2835 """Represents an OpenAPI Schema."""
@@ -33,18 +40,11 @@ class Schema(object):
3340 SchemaType .BOOLEAN : forcebool ,
3441 }
3542
36- STRING_FORMAT_CAST_CALLABLE_GETTER = {
37- SchemaFormat .NONE : text_type ,
38- SchemaFormat .DATE : format_date ,
39- SchemaFormat .DATETIME : format_datetime ,
40- SchemaFormat .BINARY : binary_type ,
41- }
42-
43- STRING_FORMAT_VALIDATOR_CALLABLE_GETTER = {
44- SchemaFormat .NONE : TypeValidator (text_type ),
45- SchemaFormat .DATE : TypeValidator (date , exclude = datetime ),
46- SchemaFormat .DATETIME : TypeValidator (datetime ),
47- SchemaFormat .BINARY : TypeValidator (binary_type ),
43+ STRING_FORMAT_CALLABLE_GETTER = {
44+ SchemaFormat .NONE : StringFormat (text_type , TypeValidator (text_type )),
45+ SchemaFormat .DATE : StringFormat (format_date , TypeValidator (date , exclude = datetime )),
46+ SchemaFormat .DATETIME : StringFormat (format_datetime , TypeValidator (datetime )),
47+ SchemaFormat .BINARY : StringFormat (binary_type , TypeValidator (binary_type )),
4848 }
4949
5050 TYPE_VALIDATOR_CALLABLE_GETTER = {
@@ -99,6 +99,7 @@ def __init__(
9999
100100 self ._all_required_properties_cache = None
101101 self ._all_optional_properties_cache = None
102+ self .custom_formatters = None
102103
103104 def __getitem__ (self , name ):
104105 return self .properties [name ]
@@ -173,11 +174,13 @@ def cast(self, value):
173174 "Failed to cast value of {0} to {1}" .format (value , self .type )
174175 )
175176
176- def unmarshal (self , value ):
177+ def unmarshal (self , value , custom_formatters = None ):
177178 """Unmarshal parameter from the value."""
178179 if self .deprecated :
179180 warnings .warn ("The schema is deprecated" , DeprecationWarning )
180181
182+ self .custom_formatters = custom_formatters
183+
181184 casted = self .cast (value )
182185
183186 if casted is None and not self .required :
@@ -195,15 +198,18 @@ def _unmarshal_string(self, value):
195198 try :
196199 schema_format = SchemaFormat (self .format )
197200 except ValueError :
198- # @todo: implement custom format unmarshalling support
199- raise OpenAPISchemaError (
200- "Unsupported {0} format unmarshalling" .format (self .format )
201- )
201+ msg = "Unsupported {0} format unmarshalling" .format (self .format )
202+ if self .custom_formatters is not None :
203+ formatstring = self .custom_formatters .get (self .format )
204+ if formatstring is None :
205+ raise OpenAPISchemaError (msg )
206+ else :
207+ raise OpenAPISchemaError (msg )
202208 else :
203- formatter = self .STRING_FORMAT_CAST_CALLABLE_GETTER [schema_format ]
209+ formatstring = self .STRING_FORMAT_CALLABLE_GETTER [schema_format ]
204210
205211 try :
206- return formatter (value )
212+ return formatstring . format (value )
207213 except ValueError :
208214 raise InvalidSchemaValue (
209215 "Failed to format value of {0} to {1}" .format (
@@ -231,7 +237,8 @@ def _unmarshal_collection(self, value):
231237 if self .items is None :
232238 raise UndefinedItemsSchema ("Undefined items' schema" )
233239
234- return list (map (self .items .unmarshal , value ))
240+ f = functools .partial (self .items .unmarshal , custom_formatters = self .custom_formatters )
241+ return list (map (f , value ))
235242
236243 def _unmarshal_object (self , value , model_factory = None ):
237244 if not isinstance (value , (dict , )):
@@ -286,7 +293,7 @@ def _unmarshal_properties(self, value, one_of_schema=None):
286293 for prop_name in extra_props :
287294 prop_value = value [prop_name ]
288295 properties [prop_name ] = self .additional_properties .unmarshal (
289- prop_value )
296+ prop_value , self . custom_formatters )
290297
291298 for prop_name , prop in iteritems (all_props ):
292299 try :
@@ -298,7 +305,7 @@ def _unmarshal_properties(self, value, one_of_schema=None):
298305 if not prop .nullable and not prop .default :
299306 continue
300307 prop_value = prop .default
301- properties [prop_name ] = prop .unmarshal (prop_value )
308+ properties [prop_name ] = prop .unmarshal (prop_value , self . custom_formatters )
302309
303310 self ._validate_properties (properties , one_of_schema = one_of_schema )
304311
@@ -405,15 +412,18 @@ def _validate_string(self, value):
405412 try :
406413 schema_format = SchemaFormat (self .format )
407414 except ValueError :
408- # @todo: implement custom format validation support
409- raise OpenAPISchemaError (
410- "Unsupported {0} format validation" .format (self .format )
411- )
415+ msg = "Unsupported {0} format validation" .format (self .format )
416+ if self .custom_formatters is not None :
417+ formatstring = self .custom_formatters .get (self .format )
418+ if formatstring is None :
419+ raise OpenAPISchemaError (msg )
420+ else :
421+ raise OpenAPISchemaError (msg )
412422 else :
413- format_validator_callable = \
414- self .STRING_FORMAT_VALIDATOR_CALLABLE_GETTER [schema_format ]
423+ formatstring = \
424+ self .STRING_FORMAT_CALLABLE_GETTER [schema_format ]
415425
416- if not format_validator_callable (value ):
426+ if not formatstring . validate (value ):
417427 raise InvalidSchemaValue (
418428 "Value of {0} not valid format of {1}" .format (
419429 value , self .format )
0 commit comments