11import sys
22import warnings
3+ from functools import partial
34from typing import Any
5+ from typing import Callable
46from typing import Dict
57from typing import Iterable
68from typing import Optional
1113 from functools import cached_property
1214else :
1315 from backports .cached_property import cached_property
16+ from jsonschema ._format import FormatChecker
1417from jsonschema .protocols import Validator
1518from openapi_schema_validator import OAS30Validator
1619
1720from openapi_core .spec import Spec
1821from openapi_core .unmarshalling .schemas .datatypes import CustomFormattersDict
19- from openapi_core .unmarshalling .schemas .datatypes import FormattersDict
22+ from openapi_core .unmarshalling .schemas .datatypes import FormatUnmarshaller
23+ from openapi_core .unmarshalling .schemas .datatypes import FormatValidator
24+ from openapi_core .unmarshalling .schemas .datatypes import UnmarshallersDict
2025from openapi_core .unmarshalling .schemas .enums import ValidationContext
2126from openapi_core .unmarshalling .schemas .exceptions import (
2227 FormatterNotFoundError ,
4752
4853
4954class SchemaValidatorsFactory :
50- CONTEXTS = {
51- ValidationContext .REQUEST : "write" ,
52- ValidationContext .RESPONSE : "read" ,
53- }
54-
5555 def __init__ (
5656 self ,
5757 schema_validator_class : Type [Validator ],
58+ base_format_checker : Optional [FormatChecker ] = None ,
59+ formatters : Optional [CustomFormattersDict ] = None ,
60+ format_unmarshallers : Optional [UnmarshallersDict ] = None ,
5861 custom_formatters : Optional [CustomFormattersDict ] = None ,
59- context : Optional [ValidationContext ] = None ,
6062 ):
6163 self .schema_validator_class = schema_validator_class
64+ if base_format_checker is None :
65+ base_format_checker = self .schema_validator_class .FORMAT_CHECKER
66+ self .base_format_checker = base_format_checker
67+ if formatters is None :
68+ formatters = {}
69+ self .formatters = formatters
70+ if format_unmarshallers is None :
71+ format_unmarshallers = {}
72+ self .format_unmarshallers = format_unmarshallers
6273 if custom_formatters is None :
6374 custom_formatters = {}
6475 self .custom_formatters = custom_formatters
65- self .context = context
6676
67- def create ( self , schema : Spec ) -> Validator :
68- resolver = schema . accessor . resolver # type: ignore
69- custom_format_checks = {
77+ @ cached_property
78+ def format_checker ( self ) -> FormatChecker :
79+ format_checks : Dict [ str , Callable [[ Any ], bool ]] = {
7080 name : formatter .validate
71- for name , formatter in self .custom_formatters .items ()
72- }
73- format_checker = build_format_checker (** custom_format_checks )
74- kwargs = {
75- "resolver" : resolver ,
76- "format_checker" : format_checker ,
81+ for formatters_list in [self .formatters , self .custom_formatters ]
82+ for name , formatter in formatters_list .items ()
7783 }
78- if self .context is not None :
79- kwargs [self .CONTEXTS [self .context ]] = True
84+ format_checks .update (
85+ {
86+ name : self ._create_checker (name )
87+ for name , _ in self .format_unmarshallers .items ()
88+ }
89+ )
90+ return build_format_checker (self .base_format_checker , ** format_checks )
91+
92+ def _create_checker (self , name : str ) -> FormatValidator :
93+ if name in self .base_format_checker .checkers :
94+ return partial (self .base_format_checker .check , format = name )
95+
96+ return lambda x : True
97+
98+ def get_checker (self , name : str ) -> FormatValidator :
99+ if name in self .format_checker .checkers :
100+ return partial (self .format_checker .check , format = name )
101+
102+ return lambda x : True
103+
104+ def create (self , schema : Spec ) -> Validator :
105+ resolver = schema .accessor .resolver # type: ignore
80106 with schema .open () as schema_dict :
81- return self .schema_validator_class (schema_dict , ** kwargs )
107+ return self .schema_validator_class (
108+ schema_dict ,
109+ resolver = resolver ,
110+ format_checker = self .format_checker ,
111+ )
112+
113+
114+ class SchemaFormatUnmarshallersFactory :
115+ def __init__ (
116+ self ,
117+ validators_factory : SchemaValidatorsFactory ,
118+ formatters : Optional [CustomFormattersDict ] = None ,
119+ format_unmarshallers : Optional [UnmarshallersDict ] = None ,
120+ custom_formatters : Optional [CustomFormattersDict ] = None ,
121+ ):
122+ self .validators_factory = validators_factory
123+ if formatters is None :
124+ formatters = {}
125+ self .formatters = formatters
126+ if format_unmarshallers is None :
127+ format_unmarshallers = {}
128+ self .format_unmarshallers = format_unmarshallers
129+ if custom_formatters is None :
130+ custom_formatters = {}
131+ self .custom_formatters = custom_formatters
132+
133+ def create (self , schema_format : str ) -> Optional [FormatUnmarshaller ]:
134+ if schema_format in self .custom_formatters :
135+ formatter = self .custom_formatters [schema_format ]
136+ return formatter .format
137+ if schema_format in self .formatters :
138+ formatter = self .formatters [schema_format ]
139+ return formatter .format
140+ if schema_format in self .format_unmarshallers :
141+ return self .format_unmarshallers [schema_format ]
142+
143+ return None
82144
83145
84146class SchemaUnmarshallersFactory :
@@ -102,23 +164,59 @@ class SchemaUnmarshallersFactory:
102164 def __init__ (
103165 self ,
104166 schema_validator_class : Type [Validator ],
167+ base_format_checker : Optional [FormatChecker ] = None ,
168+ formatters : Optional [CustomFormattersDict ] = None ,
169+ format_unmarshallers : Optional [UnmarshallersDict ] = None ,
105170 custom_formatters : Optional [CustomFormattersDict ] = None ,
106171 context : Optional [ValidationContext ] = None ,
107172 ):
108173 self .schema_validator_class = schema_validator_class
174+ self .base_format_checker = base_format_checker
109175 if custom_formatters is None :
110176 custom_formatters = {}
177+ else :
178+ warnings .warn (
179+ "custom_formatters is deprecated. "
180+ "Register new checks to FormatChecker to validate custom formats "
181+ "and add format_unmarshallers to unmarshal custom formats." ,
182+ DeprecationWarning ,
183+ )
184+ self .formatters = formatters
185+ if format_unmarshallers is None :
186+ format_unmarshallers = {}
187+ self .format_unmarshallers = format_unmarshallers
111188 self .custom_formatters = custom_formatters
112189 self .context = context
113190
114191 @cached_property
115192 def validators_factory (self ) -> SchemaValidatorsFactory :
116193 return SchemaValidatorsFactory (
117194 self .schema_validator_class ,
195+ self .base_format_checker ,
196+ self .formatters ,
197+ self .format_unmarshallers ,
198+ self .custom_formatters ,
199+ )
200+
201+ @cached_property
202+ def format_unmarshallers_factory (self ) -> SchemaFormatUnmarshallersFactory :
203+ return SchemaFormatUnmarshallersFactory (
204+ self .validators_factory ,
205+ self .formatters ,
206+ self .format_unmarshallers ,
118207 self .custom_formatters ,
119- self .context ,
120208 )
121209
210+ def get_format_validator (
211+ self , validator : Validator , schema_format : str
212+ ) -> Optional [FormatValidator ]:
213+ if schema_format in validator .format_checker .checkers :
214+ return partial (
215+ validator .format_checker .check , format = schema_format
216+ )
217+
218+ return None
219+
122220 def create (
123221 self , schema : Spec , type_override : Optional [str ] = None
124222 ) -> BaseSchemaUnmarshaller :
@@ -132,7 +230,19 @@ def create(
132230 validator = self .validators_factory .create (schema )
133231
134232 schema_format = schema .getkey ("format" )
135- formatter = self .custom_formatters .get (schema_format )
233+
234+ # FIXME: don;t raise exception on unknown format
235+ if (
236+ schema_format
237+ and schema_format
238+ not in self .validators_factory .format_checker .checkers
239+ ):
240+ raise FormatterNotFoundError (schema_format )
241+
242+ format_unmarshaller = self .format_unmarshallers_factory .create (
243+ schema_format
244+ )
245+ format_validator = self .get_format_validator (validator , schema_format )
136246
137247 schema_type = type_override or schema .getkey ("type" , "any" )
138248 if isinstance (schema_type , Iterable ) and not isinstance (
@@ -141,7 +251,7 @@ def create(
141251 return MultiTypeUnmarshaller (
142252 schema ,
143253 validator ,
144- formatter ,
254+ format_unmarshaller ,
145255 self .validators_factory ,
146256 self ,
147257 context = self .context ,
@@ -151,7 +261,7 @@ def create(
151261 return complex_klass (
152262 schema ,
153263 validator ,
154- formatter ,
264+ format_unmarshaller ,
155265 self .validators_factory ,
156266 self ,
157267 context = self .context ,
@@ -161,7 +271,7 @@ def create(
161271 return klass (
162272 schema ,
163273 validator ,
164- formatter ,
274+ format_unmarshaller ,
165275 self .validators_factory ,
166276 self ,
167277 )
0 commit comments