Skip to content

Commit 66e4cd7

Browse files
committed
mypy - more type annotations
1 parent ef1919d commit 66e4cd7

36 files changed

+1016
-594
lines changed

aiopenapi3/_types.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from . import v20, v30, v31
2+
3+
from typing import (
4+
TYPE_CHECKING,
5+
Dict,
6+
List,
7+
Sequence,
8+
Tuple,
9+
Union,
10+
TypeAlias,
11+
Type,
12+
)
13+
14+
import yaml
15+
16+
if TYPE_CHECKING:
17+
pass
18+
19+
20+
from httpx._types import RequestContent, FileTypes, RequestFiles, AuthTypes # noqa
21+
from pydantic import BaseModel
22+
23+
RequestFileParameter = Tuple[str, FileTypes]
24+
RequestFilesParameter = Sequence[RequestFileParameter]
25+
26+
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
27+
"""
28+
Define a JSON type
29+
https://github.com/python/typing/issues/182#issuecomment-1320974824
30+
"""
31+
32+
RequestData = Union[JSON, BaseModel, RequestFilesParameter]
33+
RequestParameter = Union[str, BaseModel]
34+
RequestParameters = Dict[str, RequestParameter]
35+
36+
RootType = Union[v20.Root, v30.Root, v31.Root]
37+
ReferenceType = Union[v20.Reference, v30.Reference, v31.Reference]
38+
SchemaType = Union[v20.Schema, v30.Schema, v31.Schema]
39+
DiscriminatorType = Union[v30.Discriminator, v31.Discriminator]
40+
PathItemType = Union[v20.PathItem, v30.PathItem, v31.PathItem]
41+
OperationType = Union[v20.Operation, v30.Operation, v31.Operation]
42+
ParameterType = Union[v20.Parameter, v30.Parameter, v31.Parameter]
43+
HeaderType = Union[v30.Header, v30.Header, v31.Header]
44+
RequestType = Union[v20.Request, v30.Request]
45+
MediaTypeType = Union[v30.MediaType, v31.MediaType]
46+
ExpectedType = Union[v20.Response, MediaTypeType]
47+
ResponseHeadersType = Dict[str, str]
48+
ResponseDataType = Union[BaseModel, bytes, str]
49+
50+
51+
YAMLLoaderType = Union[Type[yaml.Loader], Type[yaml.CLoader], Type[yaml.SafeLoader], Type[yaml.CSafeLoader]]
52+
53+
PrimitiveTypes = Union[str, float, int, bool]
54+
55+
__all__: List[str] = [
56+
"RootType",
57+
"SchemaType",
58+
"DiscriminatorType",
59+
"PathItemType",
60+
"OperationType",
61+
"ParameterType",
62+
"HeaderType",
63+
"RequestType",
64+
"ExpectedType",
65+
"MediaTypeType",
66+
"ResponseHeadersType",
67+
"ResponseDataType",
68+
"RequestData",
69+
"RequestParameters",
70+
"ReferenceType",
71+
"PrimitiveTypes",
72+
#
73+
"YAMLLoaderType",
74+
# httpx forwards
75+
"RequestContent",
76+
"RequestFiles",
77+
"AuthTypes",
78+
#
79+
"JSON",
80+
"RequestFilesParameter",
81+
"RequestFileParameter",
82+
]

aiopenapi3/base.py

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import typing
12
import 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

45
import re
56
import builtins
@@ -12,23 +13,24 @@
1213
else:
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

1718
from .json import JSONPointer, JSONReference
1819
from .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

2225
HTTP_METHODS = frozenset(["get", "delete", "head", "post", "put", "patch", "trace"])
2326

2427

2528
class 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

3436
class 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+
8289
class 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

286293
class ReferenceBase:
287-
pass
294+
ref: str
295+
_target: Union["SchemaType", "PathItemType"]
288296

289297

290298
class 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+
298310
class 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

435459
class 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

Comments
 (0)