diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a463f5bd..25368ca7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: pycln - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.13 + rev: v0.16.0 hooks: # Run the linter. - id: ruff-check @@ -62,7 +62,7 @@ repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.15 + rev: 0.11.32 hooks: # Update the uv lockfile - id: uv-lock diff --git a/docs/source/_ext/aioai3.py b/docs/source/_ext/aioai3.py index 7708b8b2..4cc5d6d1 100644 --- a/docs/source/_ext/aioai3.py +++ b/docs/source/_ext/aioai3.py @@ -1,8 +1,7 @@ +from docutils import nodes from sphinx.domains import Domain from sphinx.roles import XRefRole -from docutils import nodes - def resolve_url(env, name): resolve_target = getattr(env.config, "linkcode_resolve", None) diff --git a/docs/source/conf.py b/docs/source/conf.py index 118180f1..b5aee812 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,13 +6,12 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -import os +import datetime import importlib import inspect -from pathlib import Path +import os import sys -import datetime - +from pathlib import Path sys.path.append(str(p := (Path(".").absolute() / "_ext"))) assert p.exists(), f"{p} {os.getcwd()}" diff --git a/src/aiopenapi3/__init__.py b/src/aiopenapi3/__init__.py index 7cb9c190..88dba863 100644 --- a/src/aiopenapi3/__init__.py +++ b/src/aiopenapi3/__init__.py @@ -1,30 +1,29 @@ -from .version import __version__ -from .openapi import OpenAPI -from .loader import FileSystemLoader from .errors import ( - SpecError, - ReferenceResolutionError, + ContentTypeError, HTTPError, - ResponseError, HTTPStatusError, - ContentTypeError, + ReferenceResolutionError, + RequestError, ResponseDecodingError, + ResponseError, ResponseSchemaError, - RequestError, + SpecError, ) - +from .loader import FileSystemLoader +from .openapi import OpenAPI +from .version import __version__ __all__ = [ - "__version__", - "OpenAPI", + "ContentTypeError", "FileSystemLoader", - "SpecError", - "ReferenceResolutionError", "HTTPError", - "ResponseError", "HTTPStatusError", - "ContentTypeError", + "OpenAPI", + "ReferenceResolutionError", + "RequestError", "ResponseDecodingError", + "ResponseError", "ResponseSchemaError", - "RequestError", + "SpecError", + "__version__", ] diff --git a/src/aiopenapi3/__main__.py b/src/aiopenapi3/__main__.py index 7d876942..7d73de00 100644 --- a/src/aiopenapi3/__main__.py +++ b/src/aiopenapi3/__main__.py @@ -1,4 +1,5 @@ import sys + from .cli import main if __name__ == "__main__": diff --git a/src/aiopenapi3/_types.py b/src/aiopenapi3/_types.py index 18cfffa3..caf49f59 100644 --- a/src/aiopenapi3/_types.py +++ b/src/aiopenapi3/_types.py @@ -1,23 +1,17 @@ import re -from typing import TYPE_CHECKING, Union, TypeAlias, Optional, Literal from collections.abc import Sequence +from typing import Literal, TypeAlias, Union import yaml - -from httpx._types import RequestContent, FileTypes, RequestFiles, AuthTypes # noqa +from httpx._types import AuthTypes, FileTypes, RequestContent, RequestFiles from pydantic import BaseModel - from . import v20, v30, v31, v32 -if TYPE_CHECKING: - pass - - RequestFileParameter = tuple[str, FileTypes] RequestFilesParameter = Sequence[RequestFileParameter] -JSON: TypeAlias = Optional[Union[dict[str, "JSON"], list["JSON"], str, int, float, bool]] +JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None """ Define a JSON type https://github.com/python/typing/issues/182#issuecomment-1320974824 @@ -41,7 +35,7 @@ AsyncRequestType = Union[v20.AsyncRequest, v30.AsyncRequest] MediaTypeType = Union[v30.MediaType, v31.MediaType] ExpectedType = Union[v20.Response, MediaTypeType] -ResponseHeadersType = dict[str, Union[str, BaseModel, list[BaseModel]]] +ResponseHeadersType = dict[str, str | BaseModel | list[BaseModel]] ResponseDataType = Union[BaseModel, bytes, str] TagType = Union[v20.Tag, v30.Tag, v32.Tag] @@ -72,13 +66,11 @@ "RequestParameters", "ReferenceType", "PrimitiveTypes", - # "YAMLLoaderType", # httpx forwards "RequestContent", "RequestFiles", "AuthTypes", - # "JSON", "RequestFilesParameter", "RequestFileParameter", diff --git a/src/aiopenapi3/base.py b/src/aiopenapi3/base.py index 2beded1f..49029274 100644 --- a/src/aiopenapi3/base.py +++ b/src/aiopenapi3/base.py @@ -1,25 +1,22 @@ -import typing -import warnings -from typing import Any, ForwardRef, Union, cast -from collections.abc import Sequence - -import re import builtins import keyword +import re +import typing import uuid - +import warnings +from collections.abc import Sequence from pathlib import Path +from typing import Any, ForwardRef, TypeGuard, Union, cast -from typing import TypeGuard - -from pydantic import RootModel, BaseModel, TypeAdapter, Field, AnyUrl, model_validator, PrivateAttr, ConfigDict +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, PrivateAttr, RootModel, TypeAdapter, model_validator +from .errors import OperationParameterValidationError, ReferenceResolutionError from .json import JSONPointer, JSONReference -from .errors import ReferenceResolutionError, OperationParameterValidationError if typing.TYPE_CHECKING: from aiopenapi3 import OpenAPI - from ._types import SchemaType, JSON, PathItemType, ParameterType, ReferenceType, DiscriminatorType + + from ._types import JSON, DiscriminatorType, ParameterType, PathItemType, ReferenceType, SchemaType HTTP_METHODS = frozenset(["get", "delete", "head", "post", "put", "patch", "trace", "query"]) @@ -156,8 +153,8 @@ def replace(ivalue): else: if v._target is None: continue - from .model import Model from . import errors + from .model import Model if "object" not in (t := sorted(Model.types(v._target))): raise errors.SpecError(f"Discriminated Union on a schema with types {t}") diff --git a/src/aiopenapi3/cli.py b/src/aiopenapi3/cli.py index 3a01e8e6..5a5c26f1 100644 --- a/src/aiopenapi3/cli.py +++ b/src/aiopenapi3/cli.py @@ -1,22 +1,22 @@ import argparse +import cProfile import datetime -import sys -import json -import itertools -import typing -from pstats import SortKey -import pstats -import io import importlib.util -import cProfile -import tracemalloc +import io +import itertools +import json import linecache import logging +import pstats +import sys +import tracemalloc +import typing +from pstats import SortKey +import httpx2 import jmespath import yaml import yarl -import httpx2 import aiopenapi3.plugin @@ -25,12 +25,12 @@ from pathlib import Path -from .openapi import OpenAPI - -from .loader import ChainLoader, RedirectLoader, WebLoader import aiopenapi3.loader from aiopenapi3.v30.formdata import decode_content_type + +from .loader import ChainLoader, RedirectLoader, WebLoader from .log import init +from .openapi import OpenAPI if typing.TYPE_CHECKING: import aiopenapi3.request @@ -236,7 +236,7 @@ def prepare_arg(value): if auth: api.authenticate(**auth) - req: "aiopenapi3.request.RequestBase" + req: aiopenapi3.request.RequestBase if args.method: req = api.createRequest((args.operationId, args.method)) else: diff --git a/src/aiopenapi3/debug.py b/src/aiopenapi3/debug.py index 8346fc12..69a455c4 100644 --- a/src/aiopenapi3/debug.py +++ b/src/aiopenapi3/debug.py @@ -1,7 +1,9 @@ -from aiopenapi3.plugin import Document -import yaml -from pathlib import Path import json +from pathlib import Path + +import yaml + +from aiopenapi3.plugin import Document class DescriptionDocumentDumper(Document): diff --git a/src/aiopenapi3/errors.py b/src/aiopenapi3/errors.py index bd42ae52..07919ed5 100644 --- a/src/aiopenapi3/errors.py +++ b/src/aiopenapi3/errors.py @@ -1,20 +1,20 @@ +import dataclasses import typing from typing import Optional -import dataclasses import httpx2 import pydantic if typing.TYPE_CHECKING: from ._types import ( - SchemaType, - RequestType, + ExpectedType, + HeaderType, + OperationType, RequestData, RequestParameters, + RequestType, + SchemaType, ServerType, - HeaderType, - ExpectedType, - OperationType, ) @@ -79,8 +79,6 @@ class ParameterFormatError(SpecError): The specified parameter encoding is invalid for the parameter family """ - pass - class HTTPError(ErrorBase): pass @@ -204,11 +202,7 @@ def __str__(self): class HTTPClientError(HTTPStatusIndicatedError): """response code 4xx""" - pass - @dataclasses.dataclass(repr=False) class HTTPServerError(HTTPStatusIndicatedError): """response code 5xx""" - - pass diff --git a/src/aiopenapi3/extra/__init__.py b/src/aiopenapi3/extra/__init__.py index 5f3c500f..9dec5359 100644 --- a/src/aiopenapi3/extra/__init__.py +++ b/src/aiopenapi3/extra/__init__.py @@ -1,4 +1,4 @@ -from .reduce import Cull, Reduce from .cookies import Cookies +from .reduce import Cull, Reduce -__all__ = ["Cull", "Reduce", "Cookies"] +__all__ = ["Cookies", "Cull", "Reduce"] diff --git a/src/aiopenapi3/extra/cookies.py b/src/aiopenapi3/extra/cookies.py index c77f228b..32571390 100644 --- a/src/aiopenapi3/extra/cookies.py +++ b/src/aiopenapi3/extra/cookies.py @@ -1,7 +1,7 @@ -from typing import Literal import email.message import http.cookiejar import urllib.request +from typing import Literal import aiopenapi3.plugin diff --git a/src/aiopenapi3/extra/reduce.py b/src/aiopenapi3/extra/reduce.py index d52e790a..8b6a3725 100644 --- a/src/aiopenapi3/extra/reduce.py +++ b/src/aiopenapi3/extra/reduce.py @@ -1,7 +1,7 @@ -import typing -from typing import Union import logging import re +import typing +from typing import Union from ..plugin import Document, Init @@ -35,7 +35,7 @@ def __init__( super().__init__() def _reduced_paths(self, ctx: "Document.Context") -> dict: - reduced: dict[str, dict[str, "PathItemType"]] = {} + reduced: dict[str, dict[str, PathItemType]] = {} if "paths" not in ctx.document: return reduced keep_keys = {"summary", "description", "servers", "parameters"} diff --git a/src/aiopenapi3/loader.py b/src/aiopenapi3/loader.py index 1fe8ed7f..5cd4a8c0 100644 --- a/src/aiopenapi3/loader.py +++ b/src/aiopenapi3/loader.py @@ -1,12 +1,12 @@ import abc +import importlib import logging +import re import typing -import yaml + import httpx2 +import yaml import yarl -import re - -import importlib # prefer a fast json library here as we may parse large documents for i in ["orjson", "simdjson", "ujson", "json"]: @@ -19,12 +19,12 @@ assert json is not None -from pathlib import Path # noqa:E402 +from pathlib import Path from .plugin import Plugins if typing.TYPE_CHECKING: - from ._types import YAMLLoaderType, JSON + from ._types import JSON, YAMLLoaderType log = logging.getLogger("aiopenapi3.loader") @@ -42,7 +42,7 @@ class YAML12Loader(yaml.SafeLoader): """ _core_resolvers = [ - ["bool", re.compile(r"""^(?:|true|True|TRUE|false|False|FALSE)$""", re.X), list("tTfF")], + ["bool", re.compile(r"""^(?:|true|True|TRUE|false|False|FALSE)$""", re.VERBOSE), list("tTfF")], [ "int", re.compile( @@ -51,7 +51,7 @@ class YAML12Loader(yaml.SafeLoader): |[-+]?(?:[0-9]+) |0x[0-9a-fA-F]+ )$""", - re.X, + re.VERBOSE, ), list("-+0123456789"), ], @@ -61,11 +61,11 @@ class YAML12Loader(yaml.SafeLoader): r"""^(?:[-+]?(?:\.[0-9]+|[0-9]+(\.[0-9]*)?)(?:[eE][-+]?[0-9]+)? |[-+]?\.(?:inf|Inf|INF) |\.(?:nan|NaN|NAN))$""", - re.X, + re.VERBOSE, ), list("-+0123456789."), ], - ["null", re.compile(r"""^(?:~||null|Null|NULL)$""", re.X), ["~", "n", "N", ""]], + ["null", re.compile(r"""^(?:~||null|Null|NULL)$""", re.VERBOSE), ["~", "n", "N", ""]], ] """ core tags from diff --git a/src/aiopenapi3/log.py b/src/aiopenapi3/log.py index 78b5cd11..3b26b682 100644 --- a/src/aiopenapi3/log.py +++ b/src/aiopenapi3/log.py @@ -1,9 +1,8 @@ -import sys import logging.config import os -from typing import Any - +import sys from pathlib import Path +from typing import Any handlers: list[str] | None = None diff --git a/src/aiopenapi3/model.py b/src/aiopenapi3/model.py index 15a9779e..44d8ae8b 100644 --- a/src/aiopenapi3/model.py +++ b/src/aiopenapi3/model.py @@ -4,21 +4,18 @@ import logging import re import sys -from typing import Any, cast, TypeVar import typing +from typing import Annotated, Any, Literal, Optional, TypeGuard, TypeVar, Union, cast -from typing import TypeGuard - -from typing import Optional, Union, Annotated, Literal -from pydantic import BaseModel, Field, RootModel, ConfigDict import pydantic +from pydantic import BaseModel, ConfigDict, Field, RootModel -from .base import ReferenceBase, SchemaBase from . import me -from .pydanticv2 import field_class_to_schema, create_model +from .base import ReferenceBase, SchemaBase +from .pydanticv2 import create_model, field_class_to_schema if typing.TYPE_CHECKING: - from ._types import SchemaType, ReferenceType, DiscriminatorType + from ._types import DiscriminatorType, ReferenceType, SchemaType type_format_to_class: dict[str, dict[str | None, type]] = collections.defaultdict(dict) @@ -232,7 +229,6 @@ def _createAnnotations( pass else: raise ValueError() - return def model(self) -> type[BaseModel] | type[None]: if self.root: @@ -251,12 +247,12 @@ def model(self) -> type[BaseModel] | type[None]: @classmethod def collapse(cls, schema: "SchemaType", items: list["_ClassInfo"]) -> type[BaseModel]: - r: list[type[BaseModel] | type[None]] + r: list[type[BaseModel | None]] r = [i.model() for i in items] type_name = schema._get_identity("L8") if len(r) > 1: - ru = Annotated[Union[tuple(r)], Field(default=getattr(schema, "default", None))] + ru = Annotated[tuple(r), Field(default=getattr(schema, "default", None))] m: type[RootModel] = create_model(type_name, __base__=(ConfiguredRootModel[ru],), __module__=me.__name__) elif len(r) == 1: m: type[BaseModel] = cast(type[BaseModel], r[0]) @@ -363,9 +359,7 @@ def createClassInfo( if _type in Model.types(i) ) if schema.discriminator and schema.discriminator.mapping: - classinfo.root = Annotated[ - Union[t], Field(discriminator=Model.nameof(schema.discriminator.propertyName)) - ] + classinfo.root = Annotated[t, Field(discriminator=Model.nameof(schema.discriminator.propertyName))] else: if len(t): classinfo.root = Union[t] @@ -381,9 +375,7 @@ def createClassInfo( if _type in Model.types(i) ) if schema.discriminator and schema.discriminator.mapping: - classinfo.root = Annotated[ - Union[t], Field(discriminator=Model.nameof(schema.discriminator.propertyName)) - ] + classinfo.root = Annotated[t, Field(discriminator=Model.nameof(schema.discriminator.propertyName))] else: if len(t): classinfo.root = Union[t] @@ -652,18 +644,18 @@ def types(schema: "SchemaType") -> typing.Generator[str, None, None]: ) # allOf - intersection of types - allOfs: list["SchemaType"] + allOfs: list[SchemaType] if allOfs := sum([getattr(schema, "allOf", [])], []): for x in allOfs: allOf &= set(Model.types(x)) # anyOf - union of types - anyOfs: list["SchemaType"] + anyOfs: list[SchemaType] if anyOfs := sum([getattr(schema, "anyOf", [])], []): anyOf = set.union(*[set(Model.types(x)) for x in anyOfs]) if anyOfs else set() # oneOf - union of types - oneOfs: list["SchemaType"] + oneOfs: list[SchemaType] if oneOfs := sum([getattr(schema, "oneOf", [])], []): oneOf = set.union(*[set(Model.types(x)) for x in oneOfs]) if oneOfs else set() diff --git a/src/aiopenapi3/openapi.py b/src/aiopenapi3/openapi.py index e43e67f5..dff23b5d 100644 --- a/src/aiopenapi3/openapi.py +++ b/src/aiopenapi3/openapi.py @@ -1,49 +1,38 @@ -import typing - -from typing import Any, Union, cast, Optional, ForwardRef -from collections.abc import Callable -import logging import copy +import logging +import pathlib import pickle import random - -import pathlib - - -from typing import TypeGuard - +import typing +from collections.abc import Callable +from typing import Any, ForwardRef, Optional, TypeGuard, cast import httpx2 import yarl from pydantic import BaseModel -from aiopenapi3.v30.general import Reference import aiopenapi3.request +from aiopenapi3.v30.general import Reference + +from . import log, v20, v30, v31, v32 +from .base import DiscriminatorBase, ReferenceBase, RootBase, SchemaBase +from .errors import HTTPClientError, HTTPServerError, ReferenceResolutionError from .json import JSONReference -from . import v20 -from . import v30 -from . import v31 -from . import v32 -from . import log -from .request import OperationIndex, HTTP_METHODS -from .errors import ReferenceResolutionError, HTTPClientError, HTTPServerError from .loader import Loader, NullLoader +from .model import Model, is_basemodel from .plugin import Plugin, Plugins -from .base import RootBase, ReferenceBase, SchemaBase, DiscriminatorBase -from .request import RequestBase +from .request import HTTP_METHODS, OperationIndex, RequestBase from .v30.paths import Operation -from .model import is_basemodel, Model - if typing.TYPE_CHECKING: from ._types import ( - RootType, JSON, - PathItemType, - SchemaType, + HTTPMethodType, OperationType, + PathItemType, RequestType, - HTTPMethodType, + RootType, + SchemaType, ServerType, ) @@ -262,7 +251,7 @@ def __init__( Loader - loading referenced documents """ - self._createRequest: Callable[["OpenAPI", str, str, "OperationType", list["ServerType"] | None], "RequestBase"] + self._createRequest: Callable[[OpenAPI, str, str, OperationType, list[ServerType] | None], RequestBase] """ creates the Async/Request for the protocol required """ @@ -286,7 +275,7 @@ def __init__( e.g. {"BasicAuth": ("user","secret")} """ - self._documents: dict[yarl.URL, "RootType"] = dict() + self._documents: dict[yarl.URL, RootType] = dict() """ the related documents """ @@ -296,7 +285,7 @@ def __init__( server variable mapping """ - self._server_select: Callable[[list["ServerType"]], "ServerType"] = random.choice + self._server_select: Callable[[list[ServerType]], ServerType] = random.choice self._init_plugins(plugins) """ @@ -369,7 +358,6 @@ def _init_references(self): e.document = names[i] raise processed = set(values.keys()) - return # for i in self._documents.values(): # i._resolve_references(self) @@ -380,10 +368,10 @@ def _init_operationindex(self, use_operation_tags: bool) -> bool: if isinstance(self._root, v20.Root): if self.paths: - obj: "PathItemType" + obj: PathItemType for path, obj in self.paths.items(): for m in obj.model_fields_set & HTTP_METHODS: - op: "Operation" = getattr(obj, m) + op: Operation = getattr(obj, m) op._validate_path_parameters(obj, path, (m, cast(str, op.operationId))) if op.operationId is None: continue @@ -408,7 +396,7 @@ def _init_operationindex(self, use_operation_tags: bool) -> bool: for schemas in allschemas: name: str - schema: "SchemaType" + schema: SchemaType for name, schema in filter(is_schema, schemas.items()): schema._get_identity(name=name, prefix="OP") @@ -453,7 +441,7 @@ def _get_combined_attributes(schema): return ( getattr(schema, "oneOf", []) # Swagger compat + ( - list(getattr(schema, "discriminator").mapping.values()) + list(schema.discriminator.mapping.values()) if isinstance(getattr(schema, "discriminator", {}), DiscriminatorBase) else [] ) @@ -492,7 +480,7 @@ def _iterate_schemas(cls, schemas: dict[int, "SchemaType"], next_set: set[int], return processed def _init_schema_types_collect(self, only_required: bool) -> dict[str, "SchemaType"]: - byname: dict[str, "SchemaType"] = dict() + byname: dict[str, SchemaType] = dict() def is_schema(v: tuple[str, "SchemaType"]) -> bool: return isinstance(v[1], (v20.Schema, v30.Schema, v31.Schema)) @@ -536,7 +524,7 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool: elif isinstance(self._root, (v30.Root, v31.Root)): # Schema - documents = cast(Union[list[v30.Root], list[v31.Root]], self._documents.values()) + documents = cast(list[v30.Root] | list[v31.Root], self._documents.values()) components = [x.components for x in filter(has_components, documents) if x.components is not None] assert components is not None if only_required is False: @@ -613,16 +601,16 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool: return byname def _init_schema_types(self, only_required: bool) -> None: - byname: dict[str, "SchemaType"] = self._init_schema_types_collect(only_required) - byid: dict[int, "SchemaType"] = {id(i): i for i in byname.values()} + byname: dict[str, SchemaType] = self._init_schema_types_collect(only_required) + byid: dict[int, SchemaType] = {id(i): i for i in byname.values()} data: set[int] = set(byid.keys()) todo: set[int] = self._iterate_schemas(byid, data, set()) - types: dict[str, ForwardRef | type[BaseModel] | type[int] | type[str] | type[float] | type[bool]] = dict() + types: dict[str, type[BaseModel | int | str | float | bool] | ForwardRef] = dict() """ Due to Plugins (e.g. Cull/Reduce) byname may be incomplete """ - resolved: list["SchemaType"] = list( + resolved: list[SchemaType] = list( map(lambda x: byid[x]._target if isinstance(byid[x], ReferenceBase) else byid[x], todo | data) ) self.plugins.init.resolved(initialized=self._root, resolved=resolved) @@ -688,7 +676,7 @@ def url(self) -> yarl.URL: return r elif isinstance(self._root, (v30.Root, v31.Root, v32.Root)): assert self._root.servers - server: "ServerType" = self._server_select(self._root.servers) + server: ServerType = self._server_select(self._root.servers) return self._base_url.join(yarl.URL(server.createUrl(self._server_variables))) def authenticate(self, *args, **kwargs): @@ -709,7 +697,7 @@ def authenticate(self, *args, **kwargs): elif isinstance(self._root, (v30.Root, v31.Root)): v = schemes - frozenset(SecuritySchemes := self._root.components.securitySchemes) else: - raise TypeError(self._root) # noqa + raise TypeError(self._root) if v: raise ValueError(f"{self.info.title} does not accept security schemes {sorted(v)}") @@ -755,8 +743,8 @@ def createRequest(self, operationId: str | tuple[str, "HTTPMethodType"]) -> "Req :return: the returned Request is either :class:`aiopenapi3.request.RequestBase` or - in case of a httpx2.AsyncClient session_factory - :class:`aiopenapi3.request.AsyncRequestBase` """ - operation: Optional["OperationType"] = None - request: Optional["RequestType"] = None + operation: OperationType | None = None + request: RequestType | None = None try: if isinstance(operationId, str): *tags, opn = operationId.split(".") diff --git a/src/aiopenapi3/plugin.py b/src/aiopenapi3/plugin.py index b5f2acee..3b9dd8cc 100644 --- a/src/aiopenapi3/plugin.py +++ b/src/aiopenapi3/plugin.py @@ -1,18 +1,15 @@ -import dataclasses -from typing import TYPE_CHECKING, Any, Optional import abc - -from typing import TypeGuard - - -from pydantic import BaseModel +import dataclasses +from typing import TYPE_CHECKING, Any, Optional, TypeGuard import yarl +from pydantic import BaseModel if TYPE_CHECKING: + import httpx2 + from aiopenapi3 import OpenAPI - import httpx2 from .base import PathItemBase, SchemaBase from .request import RequestBase @@ -26,7 +23,7 @@ class Plugin(abc.ABC): class Context: ... def __init__(self) -> None: - self._api: Optional["OpenAPI"] = None + self._api: OpenAPI | None = None @property def api(self): @@ -53,19 +50,19 @@ class Context: def schemas(self, ctx: "Init.Context") -> "Init.Context": # pragma: no cover """modify the Schema before creating Models""" - return ctx # noqa + return ctx def resolved(self, ctx: "Init.Context") -> "Init.Context": # pragma: no cover """modify the resolved paths/PathItems before initializing the Operations""" - return ctx # noqa + return ctx def paths(self, ctx: "Init.Context") -> "Init.Context": # pragma: no cover """modify the paths/PathItems before initializing the Operations""" - return ctx # noqa + return ctx def initialized(self, ctx: "Init.Context") -> "Init.Context": # pragma: no cover """it is initialized""" - return ctx # noqa + return ctx class Document(Plugin): @@ -82,11 +79,11 @@ class Context: def loaded(self, ctx: "Document.Context") -> "Document.Context": # pragma: no cover """modify the text before parsing""" - return ctx # noqa + return ctx def parsed(self, ctx: "Document.Context") -> "Document.Context": # pragma: no cover """modify the parsed dict before …""" - return ctx # noqa + return ctx class Message(Plugin): @@ -131,31 +128,31 @@ def marshalled(self, ctx: "Message.Context") -> "Message.Context": # pragma: no """ modify the dict before sending """ - return ctx # noqa + return ctx def sending(self, ctx: "Message.Context") -> "Message.Context": # pragma: no cover """ modify the text before sending """ - return ctx # noqa + return ctx def received(self, ctx: "Message.Context") -> "Message.Context": # pragma: no cover """ modify the received text """ - return ctx # noqa + return ctx def parsed(self, ctx: "Message.Context") -> "Message.Context": # pragma: no cover """ modify the parsed dict structure """ - return ctx # noqa + return ctx def unmarshalled(self, ctx: "Message.Context") -> "Message.Context": # pragma: no cover """ modify the object """ - return ctx # noqa + return ctx class Domain: @@ -206,7 +203,7 @@ def __init__(self, plugins: list[Plugin]): def _get_domain(self, name: str, plugins: list[Plugin]) -> "Domain": domain: type[Plugin] | None if (domain := self._domains.get(name)) is None: - raise ValueError(name) # noqa + raise ValueError(name) def domain_type_f(p: Plugin) -> TypeGuard[Plugin]: return isinstance(p, domain) diff --git a/src/aiopenapi3/pydanticv2.py b/src/aiopenapi3/pydanticv2.py index 2a2af8cf..22df0395 100644 --- a/src/aiopenapi3/pydanticv2.py +++ b/src/aiopenapi3/pydanticv2.py @@ -1,9 +1,9 @@ +from datetime import date, datetime, time, timedelta from decimal import Decimal -from datetime import datetime, date, time, timedelta -from ipaddress import IPv4Network, IPv6Network, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from pathlib import Path -from typing import Any from re import Pattern +from typing import Any from uuid import UUID from pydantic import TypeAdapter @@ -39,15 +39,16 @@ (field_class, TypeAdapter(field_class).json_schema()) for field_class in field_classes_to_support ) -from pydantic import ConfigDict, BaseModel, PydanticUserError -from pydantic.main import ModelT -from typing import cast -from collections.abc import Callable import sys import types +from collections.abc import Callable +from typing import cast + +from pydantic import BaseModel, ConfigDict, PydanticUserError +from pydantic.main import ModelT -def create_model( # noqa: C901 +def create_model( model_name: str, /, *, diff --git a/src/aiopenapi3/request.py b/src/aiopenapi3/request.py index 7c2ee7dd..78f459a8 100644 --- a/src/aiopenapi3/request.py +++ b/src/aiopenapi3/request.py @@ -1,14 +1,12 @@ import abc import collections import contextlib -import typing import json import logging -from contextlib import closing +import typing +from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterator +from contextlib import aclosing, closing from typing import Any, NamedTuple, Optional, Union, cast -from collections.abc import AsyncIterator, AsyncGenerator, Generator -from collections.abc import Iterator -from contextlib import aclosing import httpx2 import pydantic @@ -16,32 +14,32 @@ from aiopenapi3.errors import ContentLengthExceededError - from .base import HTTP_METHODS, ReferenceBase +from .errors import OperationIdDuplicationError, RequestError from .version import __version__ -from .errors import RequestError, OperationIdDuplicationError if typing.TYPE_CHECKING: + from aiopenapi3 import OpenAPI + from ._types import ( - RequestParameters, - RequestData, - RequestFiles, - RequestContent, - RequestType, + JSON, AuthTypes, - SchemaType, + HTTPMethodType, + OperationType, ParameterType, PathItemType, - OperationType, - JSON, - RootType, - ServerType, + RequestContent, + RequestData, + RequestFiles, + RequestParameters, + RequestType, ResponseDataType, ResponseHeadersType, - HTTPMethodType, + RootType, + SchemaType, + ServerType, TagType, ) - from aiopenapi3 import OpenAPI log = logging.getLogger("aiopenapi3.request") @@ -49,14 +47,14 @@ class RequestParameter: def __init__(self, url: yarl.URL | str): self.url: str = str(url) - self.auth: Optional["AuthTypes"] = None + self.auth: AuthTypes | None = None self.cookies: dict[str, str] = {} # self.path = {} self.params: dict[str, str] = {} - self.content: Optional["RequestContent"] = None + self.content: RequestContent | None = None self.headers: dict[str, str] = {} self.data: dict[str, str] = {} # form-data - self.files: Optional["RequestFiles"] = {} # form-data files + self.files: RequestFiles | None = {} # form-data files self.cert: Any = None @@ -70,7 +68,7 @@ class StreamResponse(NamedTuple): class Sequencer: def __init__(self, headers: "ResponseHeadersType", stream: Iterator["JSON"], model: pydantic.BaseModel) -> None: self.headers: ResponseHeadersType = headers - self.stream: Iterator["JSON"] = stream + self.stream: Iterator[JSON] = stream self.model = model def __iter__(self) -> Iterator: @@ -115,7 +113,7 @@ def __init__( operation: "OperationType", servers: list["ServerType"] | None, ): - self.api: "OpenAPI" = api + self.api: OpenAPI = api """ OpenAPI object """ @@ -125,7 +123,7 @@ def __init__( API document root """ - self.method: "HTTPMethodType" = method + self.method: HTTPMethodType = method """ HTTP method """ @@ -135,12 +133,12 @@ def __init__( HTTP path """ - self.vars: Optional["RequestBase.Vars"] = None + self.vars: RequestBase.Vars | None = None """ Parameter & Data """ - self.operation: "OperationType" = operation + self.operation: OperationType = operation """ associated OpenAPI Operation """ @@ -150,7 +148,7 @@ def __init__( RequestParameter """ - self.servers: list["ServerType"] | None = servers + self.servers: list[ServerType] | None = servers """ Servers to use for this request """ @@ -221,7 +219,7 @@ def _build_req(self, session: httpx2.Client | httpx2.AsyncClient) -> httpx2.Requ url: yarl.URL = self.api.url if self.servers: - server: "ServerType" = self.api._server_select(self.servers) + server: ServerType = self.api._server_select(self.servers) url = self.api._base_url.join(yarl.URL(server.createUrl(self.api._server_variables))) req = session.build_request( @@ -423,8 +421,8 @@ class Sequencer: def __init__( self, headers: "ResponseHeadersType", stream: AsyncIterator["JSON"], model: pydantic.BaseModel ) -> None: - self.headers: "ResponseHeadersType" = headers - self.stream: AsyncIterator["JSON"] = stream + self.headers: ResponseHeadersType = headers + self.stream: AsyncIterator[JSON] = stream self.model = model def __aiter__(self) -> AsyncIterator: @@ -591,10 +589,8 @@ class OperationIndex: class OperationTag: def __init__(self, oi: "OperationIndex") -> None: self._oi = oi - self._operations: dict[str, tuple["HTTPMethodType", str, "OperationType", list["ServerType"] | None]] = ( - dict() - ) - self._tags: dict[str, "OperationIndex.OperationTag"] = dict() + self._operations: dict[str, tuple[HTTPMethodType, str, OperationType, list[ServerType] | None]] = dict() + self._tags: dict[str, OperationIndex.OperationTag] = dict() def __getattr__(self, item) -> RequestBase: if item in self._operations: @@ -607,9 +603,9 @@ class Iter: def __init__(self, api: "OpenAPI", use_operation_tags: bool): self.operations = [] self.r: Iterator[int] - pi: "PathItemType" + pi: PathItemType for path, pi in api.paths.items(): - op: "OperationType" + op: OperationType if pi.ref: # pi = pi.ref._target pi = cast("PathItemType", cast(ReferenceBase, pi.ref)._target) @@ -653,17 +649,17 @@ def __next__(self): return self.operations[next(self.r)] def __init__(self, api: "OpenAPI", use_operation_tags: bool): - self._api: "OpenAPI" = api - self._root: "RootType" = api._root + self._api: OpenAPI = api + self._root: RootType = api._root - self._operations: dict[str, tuple["HTTPMethodType", str, "OperationType", list["ServerType"] | None]] = dict() - self._tags: dict[str, "OperationIndex.OperationTag"] = collections.defaultdict( + self._operations: dict[str, tuple[HTTPMethodType, str, OperationType, list[ServerType] | None]] = dict() + self._tags: dict[str, OperationIndex.OperationTag] = collections.defaultdict( lambda: OperationIndex.OperationTag(self) ) - pi: "PathItemType" + pi: PathItemType for path, pi in self._root.paths.items(): - op: "OperationType" - servers: list["ServerType"] | None + op: OperationType + servers: list[ServerType] | None if pi.ref: pi = pi.ref._target for method in pi.model_fields_set & HTTP_METHODS: diff --git a/src/aiopenapi3/v20/__init__.py b/src/aiopenapi3/v20/__init__.py index 1168cb14..4b3eca50 100644 --- a/src/aiopenapi3/v20/__init__.py +++ b/src/aiopenapi3/v20/__init__.py @@ -1,12 +1,11 @@ -from .glue import Request, AsyncRequest - from .general import ExternalDocumentation, Reference -from .info import Contact, License, Info -from .parameter import Parameter, Header -from .paths import Response, Operation, PathItem, Paths +from .glue import AsyncRequest, Request +from .info import Contact, Info, License +from .parameter import Header, Parameter +from .paths import Operation, PathItem, Paths, Response from .root import Root from .schemas import Schema -from .security import SecurityScheme, SecurityRequirement +from .security import SecurityRequirement, SecurityScheme from .tag import Tag from .xml import XML @@ -41,23 +40,23 @@ def __init(): __init() __all__ = [ - "Request", + "XML", "AsyncRequest", - "ExternalDocumentation", - "Reference", "Contact", - "License", - "Info", - "Parameter", + "ExternalDocumentation", "Header", - "Response", + "Info", + "License", "Operation", + "Parameter", "PathItem", "Paths", + "Reference", + "Request", + "Response", "Root", "Schema", - "SecurityScheme", "SecurityRequirement", + "SecurityScheme", "Tag", - "XML", ] diff --git a/src/aiopenapi3/v20/general.py b/src/aiopenapi3/v20/general.py index 75551136..77f1f572 100644 --- a/src/aiopenapi3/v20/general.py +++ b/src/aiopenapi3/v20/general.py @@ -1,14 +1,13 @@ import typing from typing import Any, Union -from pydantic import Field, ConfigDict, PrivateAttr +from pydantic import ConfigDict, Field, PrivateAttr - -from ..base import ObjectExtended, ObjectBase, ReferenceBase +from ..base import ObjectBase, ObjectExtended, ReferenceBase if typing.TYPE_CHECKING: - from .schemas import Schema from .parameter import Parameter + from .schemas import Schema class ExternalDocumentation(ObjectExtended): diff --git a/src/aiopenapi3/v20/glue.py b/src/aiopenapi3/v20/glue.py index 217872d6..6625a550 100644 --- a/src/aiopenapi3/v20/glue.py +++ b/src/aiopenapi3/v20/glue.py @@ -1,18 +1,13 @@ +import json import typing -from typing import Union, cast, Optional from collections.abc import Sequence -import json - -from typing import TypeGuard - +from typing import Optional, TypeGuard, Union, cast import httpx2 import pydantic -from ..request import RequestBase, AsyncRequestBase -from ..errors import HTTPStatusError, ContentTypeError, ResponseSchemaError, ResponseDecodingError, HeadersMissingError - - +from ..errors import ContentTypeError, HeadersMissingError, HTTPStatusError, ResponseDecodingError, ResponseSchemaError +from ..request import AsyncRequestBase, RequestBase from .parameter import Parameter from .root import Root @@ -23,15 +18,15 @@ if typing.TYPE_CHECKING: from .._types import ( - RequestParameters, + HeaderType, RequestData, - ResponseHeadersType, + RequestParameters, ResponseDataType, - HeaderType, + ResponseHeadersType, ) - from .schemas import Schema from .general import Reference from .paths import Response as v20ResponseType + from .schemas import Schema def in_body(x: Union["Parameter", "Reference"]) -> TypeGuard["Parameter"]: @@ -274,7 +269,7 @@ def _process__headers( """ available = frozenset(result.headers.keys()) if missing := (required.keys() - available): - report: dict[str, "HeaderType"] = {k: required[k] for k in missing} + report: dict[str, HeaderType] = {k: required[k] for k in missing} raise HeadersMissingError(self.operation, report, result) for name, header in expected_response.headers.items(): data = headers.get(name, None) @@ -289,7 +284,7 @@ def _process_stream(self, result: httpx2.Response) -> tuple["ResponseHeadersType return headers, expected_response.schema_ def _process_request(self, result: httpx2.Response) -> tuple["ResponseHeadersType", Optional["ResponseDataType"]]: - rheaders: "ResponseHeadersType" + rheaders: ResponseHeadersType # spec enforces these are strings status_code = str(result.status_code) content_type = result.headers.get("Content-Type", None) diff --git a/src/aiopenapi3/v20/parameter.py b/src/aiopenapi3/v20/parameter.py index 6b01214c..5c3f21da 100644 --- a/src/aiopenapi3/v20/parameter.py +++ b/src/aiopenapi3/v20/parameter.py @@ -1,13 +1,13 @@ -import io import enum -from typing import Optional, Any +import io +from typing import Any, Optional from pydantic import Field -from .general import Reference -from .schemas import Schema from ..base import ObjectExtended, ParameterBase from ..errors import ParameterFormatError +from .general import Reference +from .schemas import Schema class _ParameterCodec: diff --git a/src/aiopenapi3/v20/paths.py b/src/aiopenapi3/v20/paths.py index 2927544d..40e23935 100644 --- a/src/aiopenapi3/v20/paths.py +++ b/src/aiopenapi3/v20/paths.py @@ -2,12 +2,11 @@ from pydantic import Field, model_validator -from .general import ExternalDocumentation -from .general import Reference +from ..base import ObjectExtended, OperationBase, PathItemBase, PathsBase +from .general import ExternalDocumentation, Reference from .parameter import Header, Parameter from .schemas import Schema from .security import SecurityRequirement -from ..base import ObjectExtended, PathsBase, OperationBase, PathItemBase class Response(ObjectExtended): diff --git a/src/aiopenapi3/v20/root.py b/src/aiopenapi3/v20/root.py index b585c712..0308d4bd 100644 --- a/src/aiopenapi3/v20/root.py +++ b/src/aiopenapi3/v20/root.py @@ -1,13 +1,13 @@ from pydantic import Field -from .general import Reference, ExternalDocumentation +from ..base import ObjectExtended, RootBase +from .general import ExternalDocumentation, Reference from .info import Info from .parameter import Parameter -from .paths import Response, Paths, PathItem +from .paths import PathItem, Paths, Response from .schemas import Schema -from .security import SecurityScheme, SecurityRequirement +from .security import SecurityRequirement, SecurityScheme from .tag import Tag -from ..base import ObjectExtended, RootBase class Root(ObjectExtended, RootBase): diff --git a/src/aiopenapi3/v20/schemas.py b/src/aiopenapi3/v20/schemas.py index dde8c23f..097ba370 100644 --- a/src/aiopenapi3/v20/schemas.py +++ b/src/aiopenapi3/v20/schemas.py @@ -1,10 +1,10 @@ -from typing import Union, Any, Optional +from typing import Any, Optional, Union -from pydantic import Field, model_validator, ValidatorFunctionWrapHandler, ValidationInfo +from pydantic import Field, ValidationInfo, ValidatorFunctionWrapHandler, model_validator +from ..base import ObjectExtended, SchemaBase from .general import Reference from .xml import XML -from ..base import ObjectExtended, SchemaBase class Schema(ObjectExtended, SchemaBase): diff --git a/src/aiopenapi3/v20/security.py b/src/aiopenapi3/v20/security.py index 8d227eb2..10d62997 100644 --- a/src/aiopenapi3/v20/security.py +++ b/src/aiopenapi3/v20/security.py @@ -1,4 +1,4 @@ -from typing import Union, Annotated, Literal +from typing import Annotated, Literal from pydantic import Field, RootModel @@ -32,11 +32,7 @@ class oauth2(_SecurityScheme): class SecurityScheme( RootModel[ Annotated[ - Union[ - _SecuritySchemes.basic, - _SecuritySchemes.apiKey, - _SecuritySchemes.oauth2, - ], + _SecuritySchemes.basic | _SecuritySchemes.apiKey | _SecuritySchemes.oauth2, Field(discriminator="type"), ] ] @@ -47,8 +43,6 @@ class SecurityScheme( https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-scheme-object """ - pass - class SecurityRequirement(RootModel): """ diff --git a/src/aiopenapi3/v30/__init__.py b/src/aiopenapi3/v30/__init__.py index 8983630a..f6d4a5c2 100644 --- a/src/aiopenapi3/v30/__init__.py +++ b/src/aiopenapi3/v30/__init__.py @@ -1,16 +1,15 @@ -from .glue import Request, AsyncRequest - from .components import Components from .example import Example from .general import ExternalDocumentation, Reference -from .info import Contact, License, Info +from .glue import AsyncRequest, Request +from .info import Contact, Info, License from .media import Encoding, MediaType -from .parameter import Parameter, Header -from .paths import RequestBody, Link, Response, Operation, PathItem, Paths, Callback, RuntimeExpression +from .parameter import Header, Parameter +from .paths import Callback, Link, Operation, PathItem, Paths, RequestBody, Response, RuntimeExpression from .root import Root from .schemas import Discriminator, Schema -from .security import OAuthFlow, OAuthFlows, SecurityScheme, SecurityRequirement -from .servers import ServerVariable, Server +from .security import OAuthFlow, OAuthFlows, SecurityRequirement, SecurityScheme +from .servers import Server, ServerVariable from .tag import Tag from .xml import XML @@ -58,36 +57,36 @@ def __init(): __init() __all__ = [ - "Request", + "XML", "AsyncRequest", + "Callback", "Components", - "Example", - "ExternalDocumentation", - "Reference", "Contact", - "License", - "Info", + "Discriminator", "Encoding", - "MediaType", - "Parameter", + "Example", + "ExternalDocumentation", "Header", - "RequestBody", + "Info", + "License", "Link", - "Response", + "MediaType", + "OAuthFlow", + "OAuthFlows", "Operation", + "Parameter", "PathItem", "Paths", - "Callback", - "RuntimeExpression", + "Reference", + "Request", + "RequestBody", + "Response", "Root", - "Discriminator", + "RuntimeExpression", "Schema", - "OAuthFlow", - "OAuthFlows", - "SecurityScheme", "SecurityRequirement", - "ServerVariable", + "SecurityScheme", "Server", + "ServerVariable", "Tag", - "XML", ] diff --git a/src/aiopenapi3/v30/components.py b/src/aiopenapi3/v30/components.py index 5ec859bf..071a8e61 100644 --- a/src/aiopenapi3/v30/components.py +++ b/src/aiopenapi3/v30/components.py @@ -1,11 +1,10 @@ from pydantic import Field from ..base import ObjectExtended - from .example import Example -from .paths import RequestBody, Link, Response, Callback from .general import Reference from .parameter import Header, Parameter +from .paths import Callback, Link, RequestBody, Response from .schemas import Schema from .security import SecurityScheme diff --git a/src/aiopenapi3/v30/formdata.py b/src/aiopenapi3/v30/formdata.py index 2698457d..d2da49ee 100644 --- a/src/aiopenapi3/v30/formdata.py +++ b/src/aiopenapi3/v30/formdata.py @@ -1,15 +1,15 @@ import base64 +import collections import quopri -from typing import TYPE_CHECKING, NamedTuple -from email.mime import multipart, nonmultipart from email.message import Message -import collections +from email.mime import multipart, nonmultipart +from typing import TYPE_CHECKING, NamedTuple from .parameter import encode_parameter - if TYPE_CHECKING: from pydantic import BaseModel + from .._types import MediaTypeType, SchemaType @@ -165,7 +165,6 @@ def encode_multipart_parameters( f.headers["Content-Encoding"] = codec else: """OpenAPI 3.0""" - pass data = encode_content(v, codec) diff --git a/src/aiopenapi3/v30/general.py b/src/aiopenapi3/v30/general.py index b4ce57a9..fa519208 100644 --- a/src/aiopenapi3/v30/general.py +++ b/src/aiopenapi3/v30/general.py @@ -1,15 +1,13 @@ import typing -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, PrivateAttr, ConfigDict - - -from ..base import ObjectExtended, ObjectBase, ReferenceBase +from pydantic import ConfigDict, Field, PrivateAttr +from ..base import ObjectBase, ObjectExtended, ReferenceBase if typing.TYPE_CHECKING: - from .schemas import Schema from .parameter import Parameter + from .schemas import Schema class ExternalDocumentation(ObjectExtended): diff --git a/src/aiopenapi3/v30/glue.py b/src/aiopenapi3/v30/glue.py index 461c3d49..e5d8bc6a 100644 --- a/src/aiopenapi3/v30/glue.py +++ b/src/aiopenapi3/v30/glue.py @@ -1,15 +1,16 @@ import io -from typing import Union, TYPE_CHECKING, Optional, cast, Any -from collections.abc import Sequence import json import urllib.parse +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Optional, Union, cast import httpx2 try: + import inspect + import httpx2_auth from httpx2_auth import SupportMultiAuth - import inspect except ImportError: httpx2_auth = None else: @@ -23,33 +24,33 @@ import pydantic # import pydantic.json - import aiopenapi3.v30.media -from ..request import RequestBase, AsyncRequestBase -from ..errors import HTTPStatusError, ContentTypeError, ResponseDecodingError, ResponseSchemaError, HeadersMissingError + +from ..errors import ContentTypeError, HeadersMissingError, HTTPStatusError, ResponseDecodingError, ResponseSchemaError +from ..request import AsyncRequestBase, RequestBase +from ..v31.root import Root as v31Root from .formdata import ( + MultipartParameter, + encode_multipart_parameters, parameters_from_multipart, parameters_from_urlencoded, - encode_multipart_parameters, - MultipartParameter, ) - from .root import Root as v30Root -from ..v31.root import Root as v31Root if TYPE_CHECKING: from .._types import ( - SchemaType, - RequestParameters, - RequestData, ParameterType, + RequestData, RequestFileParameter, - ResponseHeadersType, + RequestParameters, ResponseDataType, + ResponseHeadersType, + SchemaType, ) - - from .paths import Response as v30Response, MediaType as v30MediaType - from ..v31.paths import Response as v31Response, MediaType as v31MediaType + from ..v31.paths import MediaType as v31MediaType + from ..v31.paths import Response as v31Response + from .paths import MediaType as v30MediaType + from .paths import Response as v30Response v3xResponseType = Union[v30Response, v31Response] v3xMediaTypeType = Union[v30MediaType, v31MediaType] @@ -420,7 +421,7 @@ def _prepare_body(self, data_: Optional["RequestData"], mph: dict[str, str]) -> elif isinstance(value, str): rdata[name] = value else: - raise TypeError(type(value)) # noqa + raise TypeError(type(value)) self.req.files = rfiles self.req.data = rdata else: @@ -463,7 +464,7 @@ def _prepare_body(self, data_: Optional["RequestData"], mph: dict[str, str]) -> elif (ct := "application/octet-stream") in self.operation.requestBody.content: self.req.headers["Content-Type"] = ct - value: "RequestFileParameter" + value: RequestFileParameter if isinstance(data_, tuple) and len(data_) >= 2: # (name, file-like-object, …) self.req.content = data_[1] @@ -540,7 +541,7 @@ def _process__content_type( media-range = ( "*/*" / ( type "/*" ) / ( type "/" subtype ) ) *( OWS ";" OWS parameter ) """ content_type, _, encoding = content_type.partition(";") - expected_media: Optional["v3xMediaTypeType"] = ( + expected_media: v3xMediaTypeType | None = ( expected_response.content.get(content_type, None) or expected_response.content.get(content_type.partition("/")[0] + "/*", None) or expected_response.content.get("*/*", None) diff --git a/src/aiopenapi3/v30/media.py b/src/aiopenapi3/v30/media.py index 9ef6f58b..351744e3 100644 --- a/src/aiopenapi3/v30/media.py +++ b/src/aiopenapi3/v30/media.py @@ -1,10 +1,9 @@ import typing -from typing import Union, Any +from typing import Any, Union from pydantic import Field from ..base import ObjectExtended - from .example import Example from .general import Reference from .schemas import Schema diff --git a/src/aiopenapi3/v30/parameter.py b/src/aiopenapi3/v30/parameter.py index 9456af09..038aa594 100644 --- a/src/aiopenapi3/v30/parameter.py +++ b/src/aiopenapi3/v30/parameter.py @@ -1,26 +1,26 @@ -import enum import datetime import decimal +import enum +import json import typing import uuid -import json -from typing import Union, Any from collections.abc import MutableMapping +from typing import Any, Union -from pydantic import BaseModel, Field, model_validator import more_itertools +from pydantic import BaseModel, Field, model_validator -from ..base import ObjectExtended, ParameterBase as ParameterBase_, ReferenceBase +from ..base import ObjectExtended, ReferenceBase +from ..base import ParameterBase as ParameterBase_ from ..errors import ParameterFormatError - +from ..model import TYPES_SCHEMA_MAP from .example import Example from .general import Reference from .schemas import Schema -from ..model import TYPES_SCHEMA_MAP if typing.TYPE_CHECKING: - from .paths import MediaType from .._types import v3xSchemaType + from .paths import MediaType class _ParameterCodec: @@ -121,7 +121,7 @@ def _encode__label(self, name: str, type_: str, value, schema: "v3xSchemaType", # .blue value = f".{value}" elif type_ in frozenset(["number", "integer"]): - value = f".{str(value)}" + value = f".{value!s}" elif type_ == "boolean": value = f".{json.dumps(value)}" elif type_ == "null": diff --git a/src/aiopenapi3/v30/paths.py b/src/aiopenapi3/v30/paths.py index d2b48008..d166646c 100644 --- a/src/aiopenapi3/v30/paths.py +++ b/src/aiopenapi3/v30/paths.py @@ -1,14 +1,13 @@ -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, model_validator, RootModel +from pydantic import Field, RootModel, model_validator -from ..base import ObjectExtended, PathsBase, OperationBase, PathItemBase -from .general import ExternalDocumentation -from .general import Reference +from ..base import ObjectExtended, OperationBase, PathItemBase, PathsBase +from .general import ExternalDocumentation, Reference from .media import MediaType from .parameter import Header, Parameter -from .servers import Server from .security import SecurityRequirement +from .servers import Server class RequestBody(ObjectExtended): diff --git a/src/aiopenapi3/v30/root.py b/src/aiopenapi3/v30/root.py index 8e25e964..7bb6d598 100644 --- a/src/aiopenapi3/v30/root.py +++ b/src/aiopenapi3/v30/root.py @@ -1,11 +1,8 @@ from typing import Any - from pydantic import Field - from ..base import ObjectExtended, RootBase - from .components import Components from .general import Reference from .info import Info diff --git a/src/aiopenapi3/v30/schemas.py b/src/aiopenapi3/v30/schemas.py index c901ab76..ea4b0829 100644 --- a/src/aiopenapi3/v30/schemas.py +++ b/src/aiopenapi3/v30/schemas.py @@ -1,8 +1,8 @@ -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, model_validator, ConfigDict +from pydantic import ConfigDict, Field, model_validator -from ..base import ObjectExtended, SchemaBase, DiscriminatorBase +from ..base import DiscriminatorBase, ObjectExtended, SchemaBase from .general import Reference from .xml import XML diff --git a/src/aiopenapi3/v30/security.py b/src/aiopenapi3/v30/security.py index fd0cca5e..edc3df27 100644 --- a/src/aiopenapi3/v30/security.py +++ b/src/aiopenapi3/v30/security.py @@ -1,4 +1,5 @@ -from typing import Union, Annotated, Literal +from typing import Annotated, Literal + from pydantic import Field, RootModel, constr from ..base import ObjectExtended @@ -60,9 +61,7 @@ class openIdConnect(_SecurityScheme): class SecurityScheme( RootModel[ Annotated[ - Union[ - _SecuritySchemes.apiKey, _SecuritySchemes.http, _SecuritySchemes.oauth2, _SecuritySchemes.openIdConnect - ], + _SecuritySchemes.apiKey | _SecuritySchemes.http | _SecuritySchemes.oauth2 | _SecuritySchemes.openIdConnect, Field(discriminator="type"), ] ] @@ -73,8 +72,6 @@ class SecurityScheme( .. _Security Scheme: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object """ - pass - class SecurityRequirement(RootModel[dict[str, list[str]]]): """ @@ -82,5 +79,3 @@ class SecurityRequirement(RootModel[dict[str, list[str]]]): .. _SecurityRequirement: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object """ - - pass diff --git a/src/aiopenapi3/v31/__init__.py b/src/aiopenapi3/v31/__init__.py index 8a8a4a4a..252b68c0 100644 --- a/src/aiopenapi3/v31/__init__.py +++ b/src/aiopenapi3/v31/__init__.py @@ -1,14 +1,14 @@ from .components import Components from .example import Example from .general import ExternalDocumentation, Reference -from .info import Contact, License, Info +from .info import Contact, Info, License from .media import Encoding, MediaType -from .parameter import Parameter, Header -from .paths import RequestBody, Link, Response, Operation, PathItem, Paths, Callback, RuntimeExpression +from .parameter import Header, Parameter +from .paths import Callback, Link, Operation, PathItem, Paths, RequestBody, Response, RuntimeExpression from .root import Root from .schemas import Discriminator, Schema -from .security import OAuthFlow, OAuthFlows, SecurityScheme, SecurityRequirement -from .servers import ServerVariable, Server +from .security import OAuthFlow, OAuthFlows, SecurityRequirement, SecurityScheme +from .servers import Server, ServerVariable from .tag import Tag from .xml import XML diff --git a/src/aiopenapi3/v31/components.py b/src/aiopenapi3/v31/components.py index 03ce1e2d..40be317a 100644 --- a/src/aiopenapi3/v31/components.py +++ b/src/aiopenapi3/v31/components.py @@ -1,11 +1,10 @@ from pydantic import Field from ..base import ObjectExtended - from .example import Example -from .paths import RequestBody, Link, Response, Callback, PathItem from .general import Reference from .parameter import Header, Parameter +from .paths import Callback, Link, PathItem, RequestBody, Response from .schemas import Schema from .security import SecurityScheme diff --git a/src/aiopenapi3/v31/general.py b/src/aiopenapi3/v31/general.py index cab70d30..bb789bb3 100644 --- a/src/aiopenapi3/v31/general.py +++ b/src/aiopenapi3/v31/general.py @@ -1,14 +1,13 @@ import typing -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, AnyUrl, PrivateAttr, ConfigDict +from pydantic import AnyUrl, ConfigDict, Field, PrivateAttr - -from ..base import ObjectExtended, ObjectBase, ReferenceBase +from ..base import ObjectBase, ObjectExtended, ReferenceBase if typing.TYPE_CHECKING: - from .schemas import Schema from .paths import Parameter, PathItem + from .schemas import Schema class ExternalDocumentation(ObjectExtended): diff --git a/src/aiopenapi3/v31/info.py b/src/aiopenapi3/v31/info.py index ee7c5c17..8ee2e7c4 100644 --- a/src/aiopenapi3/v31/info.py +++ b/src/aiopenapi3/v31/info.py @@ -1,4 +1,4 @@ -from pydantic import Field, EmailStr, model_validator +from pydantic import EmailStr, Field, model_validator from aiopenapi3.base import ObjectExtended diff --git a/src/aiopenapi3/v31/media.py b/src/aiopenapi3/v31/media.py index 22b396fa..9957ab9b 100644 --- a/src/aiopenapi3/v31/media.py +++ b/src/aiopenapi3/v31/media.py @@ -3,11 +3,10 @@ from pydantic import Field from ..base import ObjectExtended - from .example import Example from .general import Reference -from .schemas import Schema from .parameter import Header +from .schemas import Schema class Encoding(ObjectExtended): diff --git a/src/aiopenapi3/v31/parameter.py b/src/aiopenapi3/v31/parameter.py index 484680bd..bcd2be58 100644 --- a/src/aiopenapi3/v31/parameter.py +++ b/src/aiopenapi3/v31/parameter.py @@ -1,17 +1,16 @@ import enum import typing -from typing import Union, Any +from typing import Any, Union from pydantic import Field -from ..base import ObjectExtended, ParameterBase as _ParameterBase - +from ..base import ObjectExtended +from ..base import ParameterBase as _ParameterBase +from ..v30.parameter import _ParameterCodec from .example import Example from .general import Reference from .schemas import Schema -from ..v30.parameter import _ParameterCodec - if typing.TYPE_CHECKING: from .paths import MediaType diff --git a/src/aiopenapi3/v31/paths.py b/src/aiopenapi3/v31/paths.py index c65628aa..1c384f8d 100644 --- a/src/aiopenapi3/v31/paths.py +++ b/src/aiopenapi3/v31/paths.py @@ -1,14 +1,13 @@ -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, model_validator, RootModel +from pydantic import Field, RootModel, model_validator -from ..base import ObjectExtended, PathsBase, OperationBase, PathItemBase -from .general import ExternalDocumentation -from .general import Reference +from ..base import ObjectExtended, OperationBase, PathItemBase, PathsBase +from .general import ExternalDocumentation, Reference from .media import MediaType from .parameter import Header, Parameter -from .servers import Server from .security import SecurityRequirement +from .servers import Server class RequestBody(ObjectExtended): diff --git a/src/aiopenapi3/v31/root.py b/src/aiopenapi3/v31/root.py index e596a4a5..1294f2bb 100644 --- a/src/aiopenapi3/v31/root.py +++ b/src/aiopenapi3/v31/root.py @@ -3,14 +3,12 @@ from pydantic import Field, model_validator from ..base import ObjectExtended, RootBase - +from .components import Components +from .general import Reference from .info import Info -from .paths import Paths, PathItem +from .paths import PathItem, Paths from .security import SecurityRequirement from .servers import Server - -from .components import Components -from .general import Reference from .tag import Tag diff --git a/src/aiopenapi3/v31/schemas.py b/src/aiopenapi3/v31/schemas.py index 91c8ebdb..81061fcb 100644 --- a/src/aiopenapi3/v31/schemas.py +++ b/src/aiopenapi3/v31/schemas.py @@ -1,8 +1,8 @@ -from typing import Union, Any, Optional +from typing import Any, Optional, Union -from pydantic import Field, model_validator, ConfigDict +from pydantic import ConfigDict, Field, model_validator -from ..base import ObjectExtended, SchemaBase, DiscriminatorBase +from ..base import DiscriminatorBase, ObjectExtended, SchemaBase from .xml import XML diff --git a/src/aiopenapi3/v31/security.py b/src/aiopenapi3/v31/security.py index 75c87837..76777a54 100644 --- a/src/aiopenapi3/v31/security.py +++ b/src/aiopenapi3/v31/security.py @@ -1,6 +1,6 @@ from pathlib import Path +from typing import Annotated, Literal -from typing import Union, Annotated, Literal from pydantic import Field, RootModel, constr from ..base import ObjectExtended @@ -74,13 +74,11 @@ class openIdConnect(_SecurityScheme): class SecurityScheme( RootModel[ Annotated[ - Union[ - _SecuritySchemes.apiKey, - _SecuritySchemes.http, - _SecuritySchemes.mutualTLS, - _SecuritySchemes.oauth2, - _SecuritySchemes.openIdConnect, - ], + _SecuritySchemes.apiKey + | _SecuritySchemes.http + | _SecuritySchemes.mutualTLS + | _SecuritySchemes.oauth2 + | _SecuritySchemes.openIdConnect, Field(discriminator="type"), ] ] @@ -91,8 +89,6 @@ class SecurityScheme( .. _Security Scheme: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object """ - pass - class SecurityRequirement(RootModel[dict[str, list[str]]]): """ @@ -100,5 +96,3 @@ class SecurityRequirement(RootModel[dict[str, list[str]]]): .. _SecurityRequirement: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#securityRequirementObject """ - - pass diff --git a/src/aiopenapi3/v32/__init__.py b/src/aiopenapi3/v32/__init__.py index 8a8a4a4a..252b68c0 100644 --- a/src/aiopenapi3/v32/__init__.py +++ b/src/aiopenapi3/v32/__init__.py @@ -1,14 +1,14 @@ from .components import Components from .example import Example from .general import ExternalDocumentation, Reference -from .info import Contact, License, Info +from .info import Contact, Info, License from .media import Encoding, MediaType -from .parameter import Parameter, Header -from .paths import RequestBody, Link, Response, Operation, PathItem, Paths, Callback, RuntimeExpression +from .parameter import Header, Parameter +from .paths import Callback, Link, Operation, PathItem, Paths, RequestBody, Response, RuntimeExpression from .root import Root from .schemas import Discriminator, Schema -from .security import OAuthFlow, OAuthFlows, SecurityScheme, SecurityRequirement -from .servers import ServerVariable, Server +from .security import OAuthFlow, OAuthFlows, SecurityRequirement, SecurityScheme +from .servers import Server, ServerVariable from .tag import Tag from .xml import XML diff --git a/src/aiopenapi3/v32/components.py b/src/aiopenapi3/v32/components.py index cceedc38..4bbf0642 100644 --- a/src/aiopenapi3/v32/components.py +++ b/src/aiopenapi3/v32/components.py @@ -1,14 +1,13 @@ from pydantic import Field from ..base import ObjectExtended - from .example import Example -from .paths import RequestBody, Link, Response, Callback, PathItem from .general import Reference +from .media import MediaType from .parameter import Header, Parameter +from .paths import Callback, Link, PathItem, RequestBody, Response from .schemas import Schema from .security import SecurityScheme -from .media import MediaType class Components(ObjectExtended): diff --git a/src/aiopenapi3/v32/general.py b/src/aiopenapi3/v32/general.py index 9db600a7..ee46e9d6 100644 --- a/src/aiopenapi3/v32/general.py +++ b/src/aiopenapi3/v32/general.py @@ -1,14 +1,13 @@ import typing -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, AnyUrl, PrivateAttr, ConfigDict +from pydantic import AnyUrl, ConfigDict, Field, PrivateAttr - -from ..base import ObjectExtended, ObjectBase, ReferenceBase +from ..base import ObjectBase, ObjectExtended, ReferenceBase if typing.TYPE_CHECKING: - from .schemas import Schema from .paths import Parameter, PathItem + from .schemas import Schema class ExternalDocumentation(ObjectExtended): diff --git a/src/aiopenapi3/v32/info.py b/src/aiopenapi3/v32/info.py index 33dd3090..7f407a90 100644 --- a/src/aiopenapi3/v32/info.py +++ b/src/aiopenapi3/v32/info.py @@ -1,4 +1,4 @@ -from pydantic import Field, EmailStr, model_validator +from pydantic import EmailStr, Field, model_validator from aiopenapi3.base import ObjectExtended diff --git a/src/aiopenapi3/v32/media.py b/src/aiopenapi3/v32/media.py index 825a9f1e..875c08ab 100644 --- a/src/aiopenapi3/v32/media.py +++ b/src/aiopenapi3/v32/media.py @@ -2,6 +2,7 @@ if sys.version_info < (3, 12): from typing import Any + from typing_extensions import Self else: from typing import Any, Self @@ -9,11 +10,10 @@ from pydantic import Field from ..base import ObjectExtended - from .example import Example from .general import Reference -from .schemas import Schema from .parameter import Header +from .schemas import Schema class Encoding(ObjectExtended): diff --git a/src/aiopenapi3/v32/parameter.py b/src/aiopenapi3/v32/parameter.py index 3072ff0d..fedc14e3 100644 --- a/src/aiopenapi3/v32/parameter.py +++ b/src/aiopenapi3/v32/parameter.py @@ -1,17 +1,16 @@ import enum import typing -from typing import Union, Any +from typing import Any, Union from pydantic import Field -from ..base import ObjectExtended, ParameterBase as _ParameterBase - +from ..base import ObjectExtended +from ..base import ParameterBase as _ParameterBase +from ..v30.parameter import _ParameterCodec from .example import Example from .general import Reference from .schemas import Schema -from ..v30.parameter import _ParameterCodec - if typing.TYPE_CHECKING: from .paths import MediaType diff --git a/src/aiopenapi3/v32/paths.py b/src/aiopenapi3/v32/paths.py index 578f2ea4..c71f862c 100644 --- a/src/aiopenapi3/v32/paths.py +++ b/src/aiopenapi3/v32/paths.py @@ -1,14 +1,13 @@ -from typing import Union, Any +from typing import Any, Union -from pydantic import Field, model_validator, RootModel +from pydantic import Field, RootModel, model_validator -from ..base import ObjectExtended, PathsBase, OperationBase, PathItemBase -from .general import ExternalDocumentation -from .general import Reference +from ..base import ObjectExtended, OperationBase, PathItemBase, PathsBase +from .general import ExternalDocumentation, Reference from .media import MediaType from .parameter import Header, Parameter -from .servers import Server from .security import SecurityRequirement +from .servers import Server class RequestBody(ObjectExtended): diff --git a/src/aiopenapi3/v32/root.py b/src/aiopenapi3/v32/root.py index bd3cf559..a51fc35d 100644 --- a/src/aiopenapi3/v32/root.py +++ b/src/aiopenapi3/v32/root.py @@ -4,14 +4,12 @@ from pydantic import Field, model_validator from ..base import ObjectExtended, RootBase - +from .components import Components +from .general import Reference from .info import Info -from .paths import Paths, PathItem +from .paths import PathItem, Paths from .security import SecurityRequirement from .servers import Server - -from .components import Components -from .general import Reference from .tag import Tag diff --git a/src/aiopenapi3/v32/schemas.py b/src/aiopenapi3/v32/schemas.py index 6a7b6384..690b8637 100644 --- a/src/aiopenapi3/v32/schemas.py +++ b/src/aiopenapi3/v32/schemas.py @@ -1,9 +1,9 @@ import typing -from typing import Union, Any, Optional +from typing import Any, Optional, Union -from pydantic import Field, model_validator, ConfigDict +from pydantic import ConfigDict, Field, model_validator -from ..base import ObjectExtended, SchemaBase, DiscriminatorBase +from ..base import DiscriminatorBase, ObjectExtended, SchemaBase from .xml import XML if typing.TYPE_CHECKING: diff --git a/src/aiopenapi3/v32/security.py b/src/aiopenapi3/v32/security.py index bb5c9cd4..1a007783 100644 --- a/src/aiopenapi3/v32/security.py +++ b/src/aiopenapi3/v32/security.py @@ -1,6 +1,6 @@ from pathlib import Path +from typing import Annotated, Literal -from typing import Union, Annotated, Literal from pydantic import Field, RootModel, constr from ..base import ObjectExtended @@ -79,13 +79,11 @@ class openIdConnect(_SecurityScheme): class SecurityScheme( RootModel[ Annotated[ - Union[ - _SecuritySchemes.apiKey, - _SecuritySchemes.http, - _SecuritySchemes.mutualTLS, - _SecuritySchemes.oauth2, - _SecuritySchemes.openIdConnect, - ], + _SecuritySchemes.apiKey + | _SecuritySchemes.http + | _SecuritySchemes.mutualTLS + | _SecuritySchemes.oauth2 + | _SecuritySchemes.openIdConnect, Field(discriminator="type"), ] ] @@ -97,8 +95,6 @@ class SecurityScheme( .. _here: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object """ - pass - class SecurityRequirement(RootModel[dict[str, list[str]]]): """ @@ -108,5 +104,3 @@ class SecurityRequirement(RootModel[dict[str, list[str]]]): .. _here: https://spec.openapis.org/oas/v3.2.0.html#security-requirement-object """ - - pass diff --git a/tests/api/v1/main.py b/tests/api/v1/main.py index 1a744a90..1ed1dcf8 100644 --- a/tests/api/v1/main.py +++ b/tests/api/v1/main.py @@ -1,10 +1,10 @@ import errno import starlette.status -from fastapi import APIRouter, Body, Response, Path +from fastapi import APIRouter, Body, Path, Response from fastapi.responses import JSONResponse -from .schema import Pets, Pet, PetCreate, Error +from .schema import Error, Pet, PetCreate, Pets router = APIRouter(prefix="/v1") @@ -64,11 +64,10 @@ def getPet(pet_id: int = Path(..., alias="petId")) -> Pets: for k, v in ZOO.items(): if pet_id == v.id: return v - else: - return JSONResponse( - status_code=starlette.status.HTTP_404_NOT_FOUND, - content=Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), - ) + return JSONResponse( + status_code=starlette.status.HTTP_404_NOT_FOUND, + content=Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), + ) @router.delete("/pets/{petId}", operation_id="deletePet", responses={204: {"model": None}, 404: {"model": Error}}) @@ -78,9 +77,8 @@ def deletePet(response: Response, pet_id: int = Path(..., alias="petId")) -> Pet del ZOO[k] response.status_code = starlette.status.HTTP_204_NO_CONTENT return response - else: - return JSONResponse( - status_code=starlette.status.HTTP_404_NOT_FOUND, - content=Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), - media_type="application/json; utf-8", - ) + return JSONResponse( + status_code=starlette.status.HTTP_404_NOT_FOUND, + content=Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), + media_type="application/json; utf-8", + ) diff --git a/tests/api/v1/schema.py b/tests/api/v1/schema.py index 759c8768..e06b15b7 100644 --- a/tests/api/v1/schema.py +++ b/tests/api/v1/schema.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, RootModel, Field +from pydantic import BaseModel, Field, RootModel class PetBase(BaseModel): diff --git a/tests/api/v2/main.py b/tests/api/v2/main.py index 360abb01..5f7f09f4 100644 --- a/tests/api/v2/main.py +++ b/tests/api/v2/main.py @@ -3,7 +3,7 @@ from typing import Annotated import starlette.status -from fastapi import Body, Response, Header, APIRouter, Path +from fastapi import APIRouter, Body, Header, Path, Response from fastapi.responses import JSONResponse from . import schema @@ -56,11 +56,10 @@ def getPet(pet_id: str = Path(..., alias="petId")) -> schema.Pets: for k, pet in ZOO.items(): if pet_id == pet.identifier: return pet - else: - return JSONResponse( - status_code=starlette.status.HTTP_404_NOT_FOUND, - content=schema.Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), - ) + return JSONResponse( + status_code=starlette.status.HTTP_404_NOT_FOUND, + content=schema.Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), + ) @router.delete( @@ -76,11 +75,10 @@ def deletePet( del ZOO[k] response.status_code = starlette.status.HTTP_204_NO_CONTENT return response - else: - return JSONResponse( - status_code=starlette.status.HTTP_404_NOT_FOUND, - content=schema.Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), - ) + return JSONResponse( + status_code=starlette.status.HTTP_404_NOT_FOUND, + content=schema.Error(code=errno.ENOENT, message=f"{pet_id} not found").model_dump(), + ) @router.patch("/pets", operation_id="patchPets", responses={200: {"model": schema.Pets}}) diff --git a/tests/api/v2/schema.py b/tests/api/v2/schema.py index ee7468f1..2440979b 100644 --- a/tests/api/v2/schema.py +++ b/tests/api/v2/schema.py @@ -1,10 +1,8 @@ -from datetime import timedelta import uuid +from datetime import timedelta +from typing import Annotated, Literal - -from typing import Literal, Union, Annotated - -from pydantic import BaseModel, RootModel, Field, ConfigDict +from pydantic import BaseModel, ConfigDict, Field, RootModel # from pydantic.fields import Undefined @@ -28,7 +26,7 @@ class WhiteCat(PetBase): white_name: str -class Cat(RootModel[Annotated[Union[BlackCat, WhiteCat], Field(discriminator="color")]]): +class Cat(RootModel[Annotated[BlackCat | WhiteCat, Field(discriminator="color")]]): def __getattr__(self, item): return getattr(self.root, item) @@ -42,7 +40,7 @@ class Dog(PetBase): age: timedelta -class Pet(RootModel[Annotated[Union[Cat, Dog], Field(discriminator="pet_type")]]): +class Pet(RootModel[Annotated[Cat | Dog, Field(discriminator="pet_type")]]): def __getattr__(self, item): return getattr(self.root, item) diff --git a/tests/apiv1_test.py b/tests/apiv1_test.py index 40deba3f..8c5f9b7f 100644 --- a/tests/apiv1_test.py +++ b/tests/apiv1_test.py @@ -4,17 +4,14 @@ import pytest import pytest_asyncio +# pytest.skip(allow_module_level=True) +from api.v1.main import router +from fastapi import FastAPI from hypercorn.asyncio import serve from hypercorn.config import Config -from fastapi import FastAPI - import aiopenapi3 -# pytest.skip(allow_module_level=True) - -from api.v1.main import router - app = FastAPI( version="1.0.0", title="Dorthu's Petstore", servers=[{"url": "/", "description": "Default, relative server"}] ) diff --git a/tests/apiv2_test.py b/tests/apiv2_test.py index 18dd67fe..d2608a6b 100644 --- a/tests/apiv2_test.py +++ b/tests/apiv2_test.py @@ -1,16 +1,19 @@ +import asyncio import datetime import random -import asyncio +import typing import uuid from typing import ForwardRef -import typing - import pydantic - import pytest import pytest_asyncio +from api.v2.main import router +from api.v2.schema import Dog as _Dog +# pytest.skip(allow_module_level=True) +from fastapi import FastAPI +from fastapi.responses import PlainTextResponse from hypercorn.asyncio import serve from hypercorn.config import Config @@ -18,16 +21,6 @@ from aiopenapi3 import OpenAPI from aiopenapi3.v31.schemas import Schema -from api.v2.schema import Dog as _Dog - -# pytest.skip(allow_module_level=True) - -from fastapi import FastAPI -from fastapi.responses import PlainTextResponse - - -from api.v2.main import router - app = FastAPI( version="1.0.0", title="Dorthu's Petstore", servers=[{"url": "/", "description": "Default, relative server"}] ) diff --git a/tests/cli_test.py b/tests/cli_test.py index 56e09809..5f9adee0 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,11 +1,10 @@ +import json import os import shlex from pathlib import Path -import json - -from aiopenapi3.cli import main import aiopenapi3.log +from aiopenapi3.cli import main def test_validate_cli(): diff --git a/tests/clone_test.py b/tests/clone_test.py index 56dc3eef..194ee175 100644 --- a/tests/clone_test.py +++ b/tests/clone_test.py @@ -2,7 +2,6 @@ from aiopenapi3 import OpenAPI - """ https://github.com/pydantic/pydantic/issues/6010 """ diff --git a/tests/conftest.py b/tests/conftest.py index 26bfea31..868e43f7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ -import os import dataclasses - +import os from pathlib import Path import pytest @@ -40,7 +39,7 @@ def __repr__(self): @property def schema(self): - return getattr(getattr(aiopenapi3, f"v{self.major}{self.minor}"), "Schema") + return getattr(aiopenapi3, f"v{self.major}{self.minor}").Schema @pytest.fixture(scope="session", params=[_Version(3, 0, 3), _Version(3, 1, 0)], ids=("v30", "v31")) @@ -81,6 +80,7 @@ def _get_parsed_yaml(filename, version=None): with open("tests/fixtures/" + filename) as f: raw = f.read() import yaml + from aiopenapi3.loader import YAML12Loader parsed = yaml.load(raw, Loader=YAML12Loader) diff --git a/tests/content_length_test.py b/tests/content_length_test.py index 4e4e5d35..fcc50825 100644 --- a/tests/content_length_test.py +++ b/tests/content_length_test.py @@ -1,18 +1,15 @@ import asyncio import random -from hypercorn.asyncio import serve -from hypercorn.config import Config -from fastapi import FastAPI, Request, Response, Query -from fastapi.responses import PlainTextResponse - import pytest import pytest_asyncio - +from fastapi import FastAPI, Query, Request, Response +from fastapi.responses import PlainTextResponse +from hypercorn.asyncio import serve +from hypercorn.config import Config import aiopenapi3 - app = FastAPI(version="1.0.0", title="TLS tests", servers=[{"url": "/", "description": "Default, relative server"}]) diff --git a/tests/debug_test.py b/tests/debug_test.py index bc2c1406..a286c260 100644 --- a/tests/debug_test.py +++ b/tests/debug_test.py @@ -1,5 +1,5 @@ -import pytest import httpx2 +import pytest import aiopenapi3.debug from aiopenapi3 import OpenAPI, ResponseSchemaError diff --git a/tests/error_test.py b/tests/error_test.py index ce11958a..8f0823d0 100644 --- a/tests/error_test.py +++ b/tests/error_test.py @@ -1,11 +1,15 @@ -from aiopenapi3 import OpenAPI -from aiopenapi3 import ResponseSchemaError, ContentTypeError, HTTPStatusError, ResponseDecodingError, RequestError - import httpx2 - - import pytest +from aiopenapi3 import ( + ContentTypeError, + HTTPStatusError, + OpenAPI, + RequestError, + ResponseDecodingError, + ResponseSchemaError, +) + def test_response_error(httpx_mock, with_paths_response_error_vXX): api = OpenAPI("/", with_paths_response_error_vXX, session_factory=httpx2.Client) diff --git a/tests/extra_test.py b/tests/extra_test.py index b8b535af..cac797db 100644 --- a/tests/extra_test.py +++ b/tests/extra_test.py @@ -1,15 +1,13 @@ import re import typing - from pathlib import Path import httpx2 import pytest from aiopenapi3 import OpenAPI -from aiopenapi3.loader import FileSystemLoader - from aiopenapi3.extra import Cull, Reduce +from aiopenapi3.loader import FileSystemLoader if typing.TYPE_CHECKING: from aiopenapi3.plugin import Document @@ -102,7 +100,6 @@ def test_reduced_small(): loader=FileSystemLoader(Path("tests/").absolute()), plugins=[PetStoreReduced()], ) - return @pytest.mark.parametrize("compressor", [Reduce, Cull]) diff --git a/tests/formdata_test.py b/tests/formdata_test.py index de7cf6cc..137bee8d 100644 --- a/tests/formdata_test.py +++ b/tests/formdata_test.py @@ -1,8 +1,9 @@ from pathlib import Path + import httpx2 from aiopenapi3 import OpenAPI -from aiopenapi3.v30.formdata import encode_multipart_parameters, MultipartParameter +from aiopenapi3.v30.formdata import MultipartParameter, encode_multipart_parameters def test_encode_formdata(): diff --git a/tests/forms_test.py b/tests/forms_test.py index c1d444b9..17d31d6b 100644 --- a/tests/forms_test.py +++ b/tests/forms_test.py @@ -3,11 +3,14 @@ import datetime import decimal -import aiopenapi3 - -from flask import Flask, render_template_string, Blueprint, abort +import pytest +import pytest_asyncio +from asgiref.wsgi import WsgiToAsgi +from flask import Blueprint, Flask, Response, abort, render_template_string, request from flask_bootstrap import Bootstrap4 as Bootstrap from flask_wtf import FlaskForm +from hypercorn.asyncio import serve +from hypercorn.config import Config from wtforms.fields import ( BooleanField, DateField, @@ -17,34 +20,26 @@ DecimalRangeField, EmailField, FileField, - MultipleFileField, FloatField, + HiddenField, IntegerField, IntegerRangeField, + MultipleFileField, + PasswordField, RadioField, - SelectField, SearchField, + SelectField, SelectMultipleField, - SubmitField, StringField, + SubmitField, TelField, + TextAreaField, TimeField, URLField, - HiddenField, - PasswordField, - TextAreaField, ) - from wtforms.validators import DataRequired, NumberRange -from flask import request, Response - -from asgiref.wsgi import WsgiToAsgi - -import pytest -import pytest_asyncio -from hypercorn.asyncio import serve -from hypercorn.config import Config +import aiopenapi3 # csrf.exempt(serve_test) @@ -353,8 +348,8 @@ async def test_Numbers(server, client, form_type): decimal=decimal.Decimal(1 / 3), decimalrange=decimal.Decimal(2 / 3), float=float(1 / 3), - integer=int(9), - integerrange=int(9), + integer=9, + integerrange=9, ) r = await client._.numbers(data=data) diff --git a/tests/loader_test.py b/tests/loader_test.py index 5c5817a4..62140534 100644 --- a/tests/loader_test.py +++ b/tests/loader_test.py @@ -1,11 +1,11 @@ import json - from pathlib import Path -import yarl import pytest -from aiopenapi3 import OpenAPI, FileSystemLoader, ReferenceResolutionError -from aiopenapi3.loader import Loader, Plugins, NullLoader +import yarl + +from aiopenapi3 import FileSystemLoader, OpenAPI, ReferenceResolutionError +from aiopenapi3.loader import Loader, NullLoader, Plugins SPECTPL = """ openapi: "3.0.0" @@ -77,8 +77,9 @@ def test_webload(): # FIXME https://github.com/pydantic/pydantic/issues/5730 pytest.skip() name = "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/network/resource-manager/Microsoft.Network/stable/2018-10-01/serviceEndpointPolicy.json" - from aiopenapi3.loader import WebLoader import yarl + from aiopenapi3.loader import WebLoader + loader = WebLoader(yarl.URL(name)) api = OpenAPI.load_sync(name, loader=loader) diff --git a/tests/parse_data_test.py b/tests/parse_data_test.py index 6eda87a8..a8afc8b2 100644 --- a/tests/parse_data_test.py +++ b/tests/parse_data_test.py @@ -1,7 +1,6 @@ -import pytest - import pathlib +import pytest import yarl from aiopenapi3 import FileSystemLoader, OpenAPI diff --git a/tests/parsing_test.py b/tests/parsing_test.py index 6126b531..a2017b2c 100644 --- a/tests/parsing_test.py +++ b/tests/parsing_test.py @@ -4,12 +4,11 @@ import uuid - import pytest - from pydantic import ValidationError + from aiopenapi3 import OpenAPI, ReferenceResolutionError -from aiopenapi3.errors import OperationParameterValidationError, OperationIdDuplicationError +from aiopenapi3.errors import OperationIdDuplicationError, OperationParameterValidationError URLBASE = "/" @@ -182,8 +181,8 @@ def test_parsing_schema_names(with_parsing_schema_names): def test_pydantic_classes(): - from typing import Union, ForwardRef, Annotated, Literal import types + from typing import Annotated, ForwardRef, Literal from pydantic import BaseModel, Field @@ -222,7 +221,7 @@ def test_pydantic_classes(): "model_config": {"undefined_types_warning": False}, "__annotations__": { "root": Annotated[ - Union[ForwardRef('__types["Dog"]'), ForwardRef('__types["Cat"]')], Field(discriminator="type") + ForwardRef('__types["Dog"]') | ForwardRef('__types["Cat"]'), Field(discriminator="type") ], }, } diff --git a/tests/path_test.py b/tests/path_test.py index 03d754b2..d8556fd0 100644 --- a/tests/path_test.py +++ b/tests/path_test.py @@ -4,20 +4,20 @@ import base64 import copy -import uuid import pathlib +import uuid -import pytest import httpx2 +import pytest import yarl from aiopenapi3 import OpenAPI from aiopenapi3.errors import ( - OperationParameterValidationError, - OperationIdDuplicationError, HeadersMissingError, HTTPClientError, HTTPServerError, + OperationIdDuplicationError, + OperationParameterValidationError, ) URLBASE = "/" @@ -194,7 +194,7 @@ def test_paths_security_combined(httpx2_mock, with_paths_security): with pytest.raises(ValueError, match="No security requirement satisfied"): r = api._.api_v1_auth_login_combined(data={}, parameters={}) - api.authenticate(**{"user": "theuser", "token": "thetoken"}) + api.authenticate(user="theuser", token="thetoken") r = api._.api_v1_auth_login_combined(data={}, parameters={}) api.authenticate(None) @@ -395,8 +395,6 @@ def test_paths_parameter_format(httpx2_mock, with_paths_parameter_format): assert u.parts[9] == "100" assert u.parts[10] == "3.3245460039402305e+23" - return - @pytest.mark.httpx2_mock(can_send_already_matched_responses=True) def test_paths_parameter_format_complex(httpx2_mock, with_paths_parameter_format_complex): @@ -430,7 +428,6 @@ def test_paths_response_header(httpx2_mock, with_paths_response_header): h, b = api._.types(return_headers=True) assert h["X-object"].A == 1 assert h["X-object"].B == "2" - return @pytest.mark.httpx2_mock(can_send_already_matched_responses=True) @@ -474,7 +471,7 @@ def test_paths_tags(httpx2_mock, with_paths_tags): OpenAPI(URLBASE, with_paths_tags, session_factory=httpx2.Client, use_operation_tags=False) spec = copy.deepcopy(with_paths_tags) - for k in {"/user/", "/item/"}: + for k in ("/user/", "/item/"): spec["paths"][k]["get"]["operationId"] = f"list{k[1:-1]}" api = OpenAPI(URLBASE, spec, session_factory=httpx2.Client, use_operation_tags=False) @@ -511,7 +508,7 @@ def test_paths_response_status_pattern_default(httpx2_mock, with_paths_response_ def test_paths_response_error(mocker, httpx2_mock, with_paths_response_error_vXX): - from aiopenapi3 import ResponseSchemaError, ContentTypeError, HTTPStatusError, ResponseDecodingError + from aiopenapi3 import ContentTypeError, HTTPStatusError, ResponseDecodingError, ResponseSchemaError api = OpenAPI("/", with_paths_response_error_vXX, session_factory=httpx2.Client) @@ -599,8 +596,6 @@ def test_paths_servers(httpx2_mock, with_paths_servers): request = httpx2_mock.get_requests()[-1] assert request.url.host == "operation" - return - @pytest.mark.httpx2_mock(can_send_already_matched_responses=True) def test_paths_server_variables(httpx2_mock, with_paths_server_variables): diff --git a/tests/pathv20_test.py b/tests/pathv20_test.py index dd72d23f..e3e0f966 100644 --- a/tests/pathv20_test.py +++ b/tests/pathv20_test.py @@ -1,11 +1,11 @@ import io -import uuid import urllib +import uuid -import yarl import httpx2 import pytest import python_multipart +import yarl from httpx2._multipart import MultipartStream from aiopenapi3 import OpenAPI @@ -66,7 +66,7 @@ def test_paths_security_v20_combined_securityparameters(httpx2_mock, with_paths_ with pytest.raises(ValueError, match="No security requirement satisfied"): api._.combinedSecurity(data={}, parameters={}) - api.authenticate(**{"user": "u", "token": "t"}) + api.authenticate(user="u", token="t") api._.combinedSecurity(data={}, parameters={}) api.authenticate(None) @@ -85,7 +85,7 @@ def test_paths_security_v20_alternate_securityparameters(httpx2_mock, with_paths ): api._.alternateSecurity(data={}, parameters={}) - api.authenticate(**{"user": "u", "token": "t"}) + api.authenticate(user="u", token="t") api._.alternateSecurity(data={}, parameters={}) api.authenticate(None) @@ -148,8 +148,6 @@ def test_paths_response_header_v20(httpx2_mock, with_paths_response_header_v20): # h, b = api._.get(return_headers=True) # request = httpx2_mock.get_requests()[-1] - return - @pytest.mark.httpx2_mock(can_send_already_matched_responses=True) def test_paths_parameter_format_v20(httpx2_mock, with_paths_parameter_format_v20): @@ -209,8 +207,6 @@ def on_file(file): assert (v := urllib.parse.parse_qs(request.content.decode())) is not None and v["A"] == ["a"] and v["B"] == ["5"] assert result == "ok" - return - def test_paths_response_file(httpx2_mock, with_paths_parameter_format_v20): httpx2_mock.add_response(headers={"Content-Type": "application/octet-stream"}, content=b"\x00") diff --git a/tests/petstorev3_test.py b/tests/petstorev3_test.py index d937854b..0b8589a2 100644 --- a/tests/petstorev3_test.py +++ b/tests/petstorev3_test.py @@ -7,7 +7,6 @@ from aiopenapi3.plugin import Document, Message from aiopenapi3.v20 import Reference - try: import httpx2_auth except ImportError: diff --git a/tests/pickle_test.py b/tests/pickle_test.py index 164ee115..deda4def 100644 --- a/tests/pickle_test.py +++ b/tests/pickle_test.py @@ -2,10 +2,9 @@ Tests parsing specs """ -from pathlib import Path -import pickle import copy - +import pickle +from pathlib import Path from aiopenapi3 import OpenAPI diff --git a/tests/plugin_test.py b/tests/plugin_test.py index df70f38c..8bfb03bd 100644 --- a/tests/plugin_test.py +++ b/tests/plugin_test.py @@ -5,7 +5,7 @@ import yarl from aiopenapi3 import FileSystemLoader, OpenAPI -from aiopenapi3.plugin import Init, Message, Document +from aiopenapi3.plugin import Document, Init, Message class OnInit(Init): diff --git a/tests/ref_test.py b/tests/ref_test.py index 0b51510f..d0618b79 100644 --- a/tests/ref_test.py +++ b/tests/ref_test.py @@ -7,15 +7,13 @@ allOfs are populated as expected as well. """ -from typing import ForwardRef - import typing +from typing import ForwardRef -from aiopenapi3 import OpenAPI - +from pydantic import RootModel from pydantic._internal._model_construction import ModelMetaclass -from pydantic import RootModel +from aiopenapi3 import OpenAPI def test_ref_resolution(openapi_version, petstore_expanded): diff --git a/tests/schema_test.py b/tests/schema_test.py index 4f1534d8..4df828ad 100644 --- a/tests/schema_test.py +++ b/tests/schema_test.py @@ -2,15 +2,13 @@ import typing import uuid from datetime import datetime - - from pathlib import Path -import yarl import httpx2 +import pydantic import pytest +import yarl from pydantic import ValidationError -import pydantic import aiopenapi3 from aiopenapi3 import OpenAPI @@ -304,8 +302,7 @@ class OnDocument(Document): def parsed(self, ctx): ctx.document["openapi"] = str(openapi_version) - from aiopenapi3.loader import YAML12Loader - from aiopenapi3.loader import FileSystemLoader + from aiopenapi3.loader import FileSystemLoader, YAML12Loader OpenAPI.load_file( "/test.yaml", @@ -434,7 +431,6 @@ def test_schema_discriminated_union_deep(with_schema_discriminated_union_deep): pet = Pet(dog) d = Dog.model_construct() - return None def test_schema_create_update_read(with_schema_create_update_read): @@ -461,7 +457,7 @@ def test_schema_constraints(with_schema_constraints): A("i" * i) B = (_B := api.components.schemas["B"]).get_type() - for i in range(0, 12): + for i in range(12): if _B.exclusiveMaximum > i > _B.exclusiveMinimum: B(i) else: @@ -469,7 +465,7 @@ def test_schema_constraints(with_schema_constraints): B(i) C = (_C := api.components.schemas["C"]).get_type() - for i in range(0, 12): + for i in range(12): if i % _C.multipleOf != 0: with pytest.raises(ValidationError): C(i) @@ -530,8 +526,6 @@ def test_schema_pathitems(httpx2_mock, with_schema_pathitems): r = req() r = api._.b() - return - def test_schema_baseurl_v20(with_schema_baseurl_v20): api = OpenAPI("/", with_schema_baseurl_v20, session_factory=httpx2.Client) diff --git a/tests/sequential_test.py b/tests/sequential_test.py index d396e8fb..6f7cb38f 100644 --- a/tests/sequential_test.py +++ b/tests/sequential_test.py @@ -1,17 +1,13 @@ import asyncio - from collections.abc import AsyncIterable - -from hypercorn.asyncio import serve -from hypercorn.config import Config import pydantic -from fastapi import FastAPI -from fastapi.sse import EventSourceResponse, ServerSentEvent - import pytest import pytest_asyncio - +from fastapi import FastAPI +from fastapi.sse import EventSourceResponse, ServerSentEvent +from hypercorn.asyncio import serve +from hypercorn.config import Config import aiopenapi3 diff --git a/tests/stream_test.py b/tests/stream_test.py index bc0283dd..1db01754 100644 --- a/tests/stream_test.py +++ b/tests/stream_test.py @@ -1,24 +1,19 @@ import asyncio import random import string - -from typing import Annotated - from pathlib import Path +from typing import Annotated -from hypercorn.asyncio import serve -from hypercorn.config import Config import pydantic -from fastapi import FastAPI, Request, Response, Query, UploadFile, Body -from fastapi.responses import PlainTextResponse - import pytest import pytest_asyncio - +from fastapi import Body, FastAPI, Query, Request, Response, UploadFile +from fastapi.responses import PlainTextResponse +from hypercorn.asyncio import serve +from hypercorn.config import Config import aiopenapi3 - app = FastAPI(version="1.0.0", title="TLS tests", servers=[{"url": "/", "description": "Default, relative server"}]) diff --git a/tests/tls_test.py b/tests/tls_test.py index 29b87419..4878b0d4 100644 --- a/tests/tls_test.py +++ b/tests/tls_test.py @@ -3,21 +3,18 @@ import ssl from pathlib import Path +import cryptography import httpx2 import pytest import pytest_asyncio - +import trustme +from fastapi import FastAPI, Request, Response from hypercorn.asyncio import serve from hypercorn.config import Config -import trustme -import cryptography - import aiopenapi3 from aiopenapi3.plugin import Document -from fastapi import FastAPI, Request, Response - app = FastAPI(version="1.0.0", title="TLS tests", servers=[{"url": "/", "description": "Default, relative server"}]) diff --git a/tests/v32_test.py b/tests/v32_test.py index ac233e79..b53ce198 100644 --- a/tests/v32_test.py +++ b/tests/v32_test.py @@ -1,10 +1,8 @@ import httpx2 - import pytest from pytest_httpx2 import IteratorStream -from aiopenapi3 import OpenAPI -from aiopenapi3 import v32 +from aiopenapi3 import OpenAPI, v32 def test_Components(): @@ -83,7 +81,6 @@ async def test_MediaType(httpx2_mock, with_schema_itemSchema): # prefixEncoding # itemEncoding - pass @pytest.mark.httpx2_mock(can_send_already_matched_responses=True)