1+ import typing
12import warnings
2- from typing import Optional , Any , List , Dict , ForwardRef , Union
3+ from typing import Optional , Any , List , Dict , ForwardRef , Union , Tuple , cast , Type , TypeGuard , FrozenSet , Sequence
34
45import re
56import builtins
1213else :
1314 from pathlib3x import Path
1415
15- from pydantic import BaseModel , Field , AnyUrl , model_validator , PrivateAttr
16+ from pydantic import BaseModel , Field , AnyUrl , model_validator , PrivateAttr , ConfigDict
1617
1718from .json import JSONPointer , JSONReference
1819from .errors import ReferenceResolutionError , OperationParameterValidationError
1920
20- # from . import me
21+ if typing .TYPE_CHECKING :
22+ from aiopenapi3 import OpenAPI
23+ from ._types import SchemaType , JSON , PathItemType , ParameterType , ReferenceType , DiscriminatorType
2124
2225HTTP_METHODS = frozenset (["get" , "delete" , "head" , "post" , "put" , "patch" , "trace" ])
2326
2427
2528class ObjectBase (BaseModel ):
2629 """
27- The base class for all schema objects. Includes helpers for common schema-
28- related functions.
30+ The base class for all schema objects. Includes helpers for common schema-related functions.
2931 """
3032
31- model_config = dict ( arbitrary_types_allowed = False , extra = "forbid" )
33+ model_config = ConfigDict ( extra = "forbid" )
3234
3335
3436class ObjectExtended (ObjectBase ):
@@ -79,33 +81,38 @@ def values(self):
7981 return self .paths .values ()
8082
8183
84+ class PathItemBase :
85+ # parameters: Optional[List[Union["ParameterBase", "ReferenceBase"]]]
86+ parameters : List [Any ]
87+
88+
8289class RootBase :
8390 @staticmethod
8491 def resolve (api : "OpenAPI" , root : "RootBase" , obj , _PathItem , _Reference ):
8592 from . import v20 , v30 , v31
8693
8794 def replaceSchemaReference (data ):
88- def replace (value ):
89- if not isinstance (value , SchemaBase ):
90- return value
91- r = getattr (value , "ref" , None )
92- if not r :
93- return value
94- return _Reference .model_construct (ref = r )
95+ def replace (ivalue ):
96+ if not isinstance (ivalue , SchemaBase ):
97+ return ivalue
98+ ir = getattr (ivalue , "ref" , None )
99+ if not ir :
100+ return ivalue
101+ return _Reference .model_construct (ref = ir )
95102
96103 if isinstance (data , list ):
97- for idx , item in enumerate (data ):
98- n = replace (item )
99- if item != n :
104+ for idx , _item in enumerate (data ):
105+ n = replace (_item )
106+ if _item != n :
100107 data [idx ] = n
101108
102109 elif isinstance (data , dict ):
103110 new = dict ()
104- for k , v in data .items ():
105- n = replace (v ) # Swagger 2.0 Schema.ref resolver …
106- if v != n :
107- v = n
108- new [k ] = v
111+ for _k , _v in data .items ():
112+ n = replace (_v ) # Swagger 2.0 Schema.ref resolver …
113+ if _v != n :
114+ _v = n
115+ new [_k ] = _v
109116 if new :
110117 data .update (new )
111118
@@ -125,13 +132,13 @@ def replace(value):
125132 setattr (obj , slot , value )
126133
127134 if isinstance (root , (v30 .root .Root , v31 .root .Root )):
128- if isinstance (value , DiscriminatorBase ):
135+ if isinstance (value , ( v30 . Discriminator , v31 . Discriminator ) ):
129136 """
130137 Discriminated Unions - implementing undefined behavior
131138 sub-schemas not having the discriminated property "const" or enum or mismatching the mapping
132139 are a problem
133140 pydantic requires these to be mapping Literal and unique
134- creating a seperate Model for the sub-schema with the mapping Literal is possible
141+ creating a separate Model for the sub-schema with the mapping Literal is possible
135142 but makes using them horrible
136143
137144 we warn about it and force feed the mapping Literal to make it work
@@ -153,7 +160,7 @@ def replace(value):
153160 from .model import Model
154161 from . import errors
155162
156- if not "object" in (t := sorted (Model .types (v ._target ))):
163+ if "object" not in (t := sorted (Model .types (v ._target ))):
157164 raise errors .SpecError (f"Discriminated Union on a schema with types { t } " )
158165
159166 if (p := v .properties .get (value .propertyName , None )) is None :
@@ -186,7 +193,7 @@ def replace(value):
186193 """
187194 ref fields embedded in objects -> replace the object with a Reference object
188195
189- PathItem Ref is ambigous
196+ PathItem Ref is ambiguous
190197 https://github.com/OAI/OpenAPI-Specification/issues/2635
191198 """
192199 if isinstance (root , (v20 .root .Root , v30 .root .Root , v31 .root .Root )):
@@ -284,7 +291,8 @@ def resolve_jp(self, jp):
284291
285292
286293class ReferenceBase :
287- pass
294+ ref : str
295+ _target : Union ["SchemaType" , "PathItemType" ]
288296
289297
290298class ParameterBase :
@@ -295,17 +303,21 @@ class DiscriminatorBase:
295303 pass
296304
297305
306+ # propertyName: str
307+ # mapping: Dict[str, str] = Field(default_factory=dict)
308+
309+
298310class SchemaBase (BaseModel ):
299311 """
300312 The Base for the Schema
301313 """
302314
303- _model_type : "BaseModel" = PrivateAttr (default = None )
315+ _model_type : Type [ "BaseModel" ] = PrivateAttr (default = None )
304316 """
305317 use to store _the_ model
306318 """
307319
308- _model_types : List ["BaseModel" ] = PrivateAttr (default_factory = list )
320+ _model_types : List [Type [ "BaseModel" ] ] = PrivateAttr (default_factory = list )
309321 """
310322 sub-schemas add the properties of the parent to the model of the subschemas
311323
@@ -332,6 +344,8 @@ class SchemaBase(BaseModel):
332344 The _identity attribute is set during OpenAPI.__init__ and used to create the class name in get_type()
333345 """
334346
347+ # items: Optional[Union["SchemaType", List["SchemaType"]]]
348+
335349 def __getstate__ (self ):
336350 """
337351 pickle can't do the _model_type - remove from pydantic's __getstate__
@@ -353,7 +367,7 @@ def _get_identity(self, prefix="XLS", name=None):
353367 if name is None :
354368 name = self .title
355369 if name :
356- n = re .sub (r"[^\w] " , "_" , name , flags = re .ASCII )
370+ n = re .sub (r"\W " , "_" , name , flags = re .ASCII )
357371 else :
358372 n = str (uuid .uuid4 ()).replace ("-" , "_" )
359373
@@ -374,28 +388,35 @@ def _get_identity(self, prefix="XLS", name=None):
374388 return self ._identity
375389
376390 def set_type (
377- self , names : List [str ] = None , discriminators : List [DiscriminatorBase ] = None , extra : "SchemaBase" = None
378- ) -> BaseModel :
391+ self ,
392+ names : List [str ] | None = None ,
393+ discriminators : Sequence [DiscriminatorBase ] | None = None ,
394+ extra : Optional ["SchemaBase" ] = None ,
395+ ) -> Type [BaseModel ]:
379396 from .model import Model
380397
381398 if extra is None :
382- self ._model_type = Model .from_schema (self , names , discriminators )
399+ self ._model_type = Model .from_schema (
400+ cast ("SchemaType" , self ), names , cast (List ["DiscriminatorType" ], discriminators )
401+ )
383402 return self ._model_type
384403 else :
385404 identity = self ._identity
386405 self ._identity = f"{ identity } .c{ len (self ._model_types )} "
387- r = Model .from_schema (self , names , discriminators , extra )
406+ r = Model .from_schema (
407+ cast ("SchemaType" , self ), names , cast (List ["DiscriminatorType" ], discriminators ), extra
408+ )
388409 self ._model_types .append (r )
389410 self ._identity = identity
390411 return r
391412
392413 def get_type (
393414 self ,
394- names : List [str ] = None ,
395- discriminators : List [DiscriminatorBase ] = None ,
396- extra : "SchemaBase" = None ,
415+ names : List [str ] | None = None ,
416+ discriminators : Sequence [DiscriminatorBase ] | None = None ,
417+ extra : Optional [ "SchemaBase" ] = None ,
397418 fwdref : bool = False ,
398- ) -> Union [BaseModel , ForwardRef ]:
419+ ) -> Union [Type [ BaseModel ] , ForwardRef ]:
399420 if fwdref :
400421 if "module" in ForwardRef .__init__ .__code__ .co_varnames :
401422 # FIXME Python < 3.9 compat
@@ -409,7 +430,7 @@ def get_type(
409430 else :
410431 return self .set_type (names , discriminators , extra )
411432
412- def model (self , data : Dict ) :
433+ def model (self , data : "JSON" ) -> Union [ BaseModel , List [ BaseModel ]] :
413434 """
414435 Generates a model representing this schema from the given data.
415436
@@ -419,30 +440,44 @@ def model(self, data: Dict):
419440 :returns: A new :any:`Model` created in this Schema's type from the data.
420441 :rtype: self.get_type()
421442 """
443+
422444 if self .type in ("string" , "number" , "boolean" , "integer" ):
423445 assert len (self .properties ) == 0
424- t = Model .typeof (self )
446+ t = Model .typeof (cast ( "SchemaType" , self ) )
425447 # data from Headers will be of type str
426448 if not isinstance (data , t ):
427449 return t (data )
428450 return data
429451 elif self .type == "array" :
430- return [self .items .model (i ) for i in data ]
452+ items = cast ("SchemaType" , self .items )
453+ return [items .model (i ) for i in cast (List ["JSON" ], data )]
431454 else :
432- return self .get_type ().model_validate (data )
455+ type_ = cast ("SchemaType" , self .get_type ())
456+ return type_ .model_validate (data )
433457
434458
435459class OperationBase :
436- def _validate_path_parameters (self , pi : "PathItem" , path_ , loc ):
460+ # parameters: Optional[List[ParameterBase | ReferenceBase]]
461+ parameters : List [Any ]
462+
463+ def _validate_path_parameters (self , pi_ : "PathItemBase" , path_ : str , loc : Tuple [Any , str ]):
437464 """
438465 Ensures that all parameters for this path are valid
439466 """
440467 assert isinstance (path_ , str )
441468 # FIXME { and } are allowed in parameter name, regex can't handle this e.g. {name}}
442469 path = frozenset (re .findall (r"{([a-zA-Z0-9\-\._~]+)}" , path_ ))
443470
444- op = frozenset (map (lambda x : x .name , filter (lambda c : c .in_ == "path" , self .parameters )))
445- pi = frozenset (map (lambda x : x .name , filter (lambda c : c .in_ == "path" , pi .parameters )))
471+ def parameter_in_path (c : Union ["ParameterType" , "ReferenceType" ]) -> TypeGuard ["ParameterType" ]:
472+ if isinstance (c , ParameterBase ):
473+ return c .in_ == "path"
474+ assert isinstance (c , ReferenceBase )
475+ return parameter_in_path (c ._target )
476+
477+ assert self .parameters is not None
478+ assert pi_ .parameters is not None
479+ op : FrozenSet [str ] = frozenset (map (lambda x : x .name , filter (parameter_in_path , self .parameters )))
480+ pi : FrozenSet [str ] = frozenset (map (lambda x : x .name , filter (parameter_in_path , pi_ .parameters )))
446481
447482 invalid = sorted (filter (lambda x : re .match (r"^([a-zA-Z0-9\-\._~]+)$" , x ) is None or len (x ) == 0 , op | pi ))
448483 if invalid :
0 commit comments