From 7a819b18f0b7e1c3dc019a6f4815e61565cb7004 Mon Sep 17 00:00:00 2001 From: luolingchun Date: Sat, 9 Aug 2025 13:00:47 +0800 Subject: [PATCH 1/2] Drop support for Python 3.9 --- .github/workflows/tests.yml | 2 +- README.md | 2 +- docs/index.zh.md | 2 +- examples/api_blueprint_demo.py | 3 +- examples/api_view_demo.py | 5 +- examples/async_demo.py | 5 +- examples/rest_demo.py | 9 +- flask_openapi3/blueprint.py | 28 ++-- flask_openapi3/models/__init__.py | 30 ++--- flask_openapi3/models/components.py | 22 ++-- flask_openapi3/models/contact.py | 7 +- flask_openapi3/models/discriminator.py | 3 +- flask_openapi3/models/encoding.py | 10 +- flask_openapi3/models/example.py | 10 +- .../models/external_documentation.py | 3 +- flask_openapi3/models/header.py | 5 +- flask_openapi3/models/info.py | 11 +- flask_openapi3/models/license.py | 5 +- flask_openapi3/models/link.py | 14 +- flask_openapi3/models/media_type.py | 10 +- flask_openapi3/models/oauth_flow.py | 7 +- flask_openapi3/models/oauth_flows.py | 9 +- flask_openapi3/models/operation.py | 25 ++-- flask_openapi3/models/parameter.py | 24 ++-- flask_openapi3/models/path_item.py | 12 +- flask_openapi3/models/request_body.py | 5 +- flask_openapi3/models/response.py | 7 +- flask_openapi3/models/responses.py | 3 +- flask_openapi3/models/schema.py | 76 +++++------ flask_openapi3/models/security_scheme.py | 15 +-- flask_openapi3/models/server.py | 5 +- flask_openapi3/models/server_variable.py | 5 +- flask_openapi3/models/tag.py | 6 +- flask_openapi3/models/validation_error.py | 6 +- flask_openapi3/models/xml.py | 7 +- flask_openapi3/openapi.py | 45 +++---- flask_openapi3/request.py | 18 +-- flask_openapi3/scaffold.py | 122 +++++++++--------- flask_openapi3/types.py | 22 ++-- flask_openapi3/utils.py | 26 ++-- flask_openapi3/view.py | 32 ++--- pyproject.toml | 6 +- tests/test_api_blueprint.py | 5 +- tests/test_api_view.py | 5 +- tests/test_async.py | 5 +- tests/test_form.py | 8 +- tests/test_model_extra.py | 3 +- tests/test_openapi.py | 6 +- tests/test_request.py | 9 +- tests/test_restapi.py | 7 +- tests/test_validate_request.py | 3 +- 51 files changed, 343 insertions(+), 377 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b2b099ab..86dbbc8f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13" ] flask-version: [ "Flask>=2.0,<3.0", "Flask>=3.0" ] env: PYTHONPATH: . diff --git a/README.md b/README.md index f58d9f60..8be8d1af 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The key features are: ## Requirements -Python 3.9+ +Python 3.10+ flask-openapi3 is dependent on the following libraries: diff --git a/docs/index.zh.md b/docs/index.zh.md index 84e3907f..60232846 100644 --- a/docs/index.zh.md +++ b/docs/index.zh.md @@ -32,7 +32,7 @@ ## 依赖 -Python 3.9+ +Python 3.10+ flask-openapi3 依赖以下库: diff --git a/examples/api_blueprint_demo.py b/examples/api_blueprint_demo.py index 11c8dda8..a9a9242d 100644 --- a/examples/api_blueprint_demo.py +++ b/examples/api_blueprint_demo.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2021/6/6 14:05 -from typing import Optional from pydantic import BaseModel, Field @@ -37,7 +36,7 @@ class Unauthorized(BaseModel): class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/examples/api_view_demo.py b/examples/api_view_demo.py index c35d5846..8b33f185 100644 --- a/examples/api_view_demo.py +++ b/examples/api_view_demo.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2022/10/18 9:00 -from typing import Optional from pydantic import BaseModel, Field @@ -22,11 +21,11 @@ class BookPath(BaseModel): class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/examples/async_demo.py b/examples/async_demo.py index 897395e3..c792569b 100644 --- a/examples/async_demo.py +++ b/examples/async_demo.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2022/11/30 14:55 -from typing import Optional from pydantic import BaseModel, Field @@ -17,11 +16,11 @@ class Query(BaseModel): class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/examples/rest_demo.py b/examples/rest_demo.py index 90faa073..0765a59d 100644 --- a/examples/rest_demo.py +++ b/examples/rest_demo.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2021/4/28 11:24 from http import HTTPStatus -from typing import Optional from pydantic import BaseModel, Field @@ -45,25 +44,25 @@ class BookPath(BaseModel): class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") s_list: list[str] = Field(None, alias="s_list[]", description="some array") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") class BookBodyWithID(BaseModel): bid: int = Field(..., description="book id") - age: Optional[int] = Field(None, ge=2, le=4, description="Age") + age: int | None = Field(None, ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") class BookResponse(BaseModel): code: int = Field(0, description="Status Code") message: str = Field("ok", description="Exception Information") - data: Optional[BookBodyWithID] + data: BookBodyWithID | None @app.get( diff --git a/flask_openapi3/blueprint.py b/flask_openapi3/blueprint.py index 6d5e44b8..ba7e95c7 100644 --- a/flask_openapi3/blueprint.py +++ b/flask_openapi3/blueprint.py @@ -2,7 +2,7 @@ # @Author : llc # @Time : 2022/4/1 16:54 import inspect -from typing import Any, Callable, Optional +from typing import Any, Callable from flask import Blueprint @@ -28,9 +28,9 @@ def __init__( name: str, import_name: str, *, - abp_tags: Optional[list[Tag]] = None, - abp_security: Optional[list[dict[str, list[str]]]] = None, - abp_responses: Optional[ResponseDict] = None, + abp_tags: list[Tag] | None = None, + abp_security: list[dict[str, list[str]]] | None = None, + abp_responses: ResponseDict | None = None, doc_ui: bool = True, operation_id_callback: Callable = get_operation_id_for_path, **kwargs: Any, @@ -111,16 +111,16 @@ def _collect_openapi_info( rule: str, func: Callable, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, method: str = HTTPMethod.GET, ) -> ParametersTuple: diff --git a/flask_openapi3/models/__init__.py b/flask_openapi3/models/__init__.py index d3e82250..da885e9b 100644 --- a/flask_openapi3/models/__init__.py +++ b/flask_openapi3/models/__init__.py @@ -57,13 +57,13 @@ class APISpec(BaseModel): openapi: str info: Info - servers: Optional[list[Server]] = None + servers: list[Server] | None = None paths: Paths - components: Optional[Components] = None - security: Optional[list[SecurityRequirement]] = None - tags: Optional[list[Tag]] = None - externalDocs: Optional[ExternalDocumentation] = None - webhooks: Optional[dict[str, Union[PathItem, Reference]]] = None + components: Components | None = None + security: list[SecurityRequirement] | None = None + tags: list[Tag] | None = None + externalDocs: ExternalDocumentation | None = None + webhooks: dict[str, PathItem | Reference] | None = None model_config = {"extra": "allow"} @@ -73,15 +73,15 @@ class OAuthConfig(BaseModel): https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md#oauth-20-configuration """ - clientId: Optional[str] = None - clientSecret: Optional[str] = None - realm: Optional[str] = None - appName: Optional[str] = None - scopeSeparator: Optional[str] = None - scopes: Optional[str] = None - additionalQueryStringParams: Optional[dict[str, str]] = None - useBasicAuthenticationWithAccessCodeGrant: Optional[bool] = False - usePkceWithAuthorizationCodeGrant: Optional[bool] = False + clientId: str | None = None + clientSecret: str | None = None + realm: str | None = None + appName: str | None = None + scopeSeparator: str | None = None + scopes: str | None = None + additionalQueryStringParams: dict[str, str] | None = None + useBasicAuthenticationWithAccessCodeGrant: bool | None = False + usePkceWithAuthorizationCodeGrant: bool | None = False class RawModel(Request): diff --git a/flask_openapi3/models/components.py b/flask_openapi3/models/components.py index d2ebe1fa..efc142b7 100644 --- a/flask_openapi3/models/components.py +++ b/flask_openapi3/models/components.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:36 -from typing import Any, Optional, Union +from typing import Any from pydantic import BaseModel, Field @@ -23,15 +23,15 @@ class Components(BaseModel): https://spec.openapis.org/oas/v3.1.0#components-object """ - schemas: Optional[dict[str, Union[Reference, Schema]]] = Field(None) - responses: Optional[dict[str, Union[Response, Reference]]] = None - parameters: Optional[dict[str, Union[Parameter, Reference]]] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - requestBodies: Optional[dict[str, Union[RequestBody, Reference]]] = None - headers: Optional[dict[str, Union[Header, Reference]]] = None - securitySchemes: Optional[dict[str, Union[SecurityScheme, dict[str, Any]]]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None - callbacks: Optional[dict[str, Union[Callback, Reference]]] = None - pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None + schemas: dict[str, Reference | Schema] | None = Field(None) + responses: dict[str, Response | Reference] | None = None + parameters: dict[str, Parameter | Reference] | None = None + examples: dict[str, Example | Reference] | None = None + requestBodies: dict[str, RequestBody | Reference] | None = None + headers: dict[str, Header | Reference] | None = None + securitySchemes: dict[str, SecurityScheme | dict[str, Any]] | None = None + links: dict[str, Link | Reference] | None = None + callbacks: dict[str, Callback | Reference] | None = None + pathItems: dict[str, PathItem | Reference] | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/contact.py b/flask_openapi3/models/contact.py index 6ad53b59..c8e5ea84 100644 --- a/flask_openapi3/models/contact.py +++ b/flask_openapi3/models/contact.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:37 -from typing import Optional from pydantic import BaseModel @@ -11,8 +10,8 @@ class Contact(BaseModel): https://spec.openapis.org/oas/v3.1.0#contact-object """ - name: Optional[str] = None - url: Optional[str] = None - email: Optional[str] = None + name: str | None = None + url: str | None = None + email: str | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/discriminator.py b/flask_openapi3/models/discriminator.py index 141b5176..a9901314 100644 --- a/flask_openapi3/models/discriminator.py +++ b/flask_openapi3/models/discriminator.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:41 -from typing import Optional from pydantic import BaseModel @@ -12,6 +11,6 @@ class Discriminator(BaseModel): """ propertyName: str - mapping: Optional[dict[str, str]] = None + mapping: dict[str, str] | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/encoding.py b/flask_openapi3/models/encoding.py index 8e8598d2..d1455a97 100644 --- a/flask_openapi3/models/encoding.py +++ b/flask_openapi3/models/encoding.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:41 -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Union from pydantic import BaseModel @@ -18,10 +18,10 @@ class Encoding(BaseModel): https://spec.openapis.org/oas/v3.1.0#encoding-object """ - contentType: Optional[str] = None - headers: Optional[dict[str, Union[Header, Reference]]] = None - style: Optional[str] = None - explode: Optional[bool] = None + contentType: str | None = None + headers: dict[str, Union[Header, Reference]] | None = None + style: str | None = None + explode: bool | None = None allowReserved: bool = False model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/example.py b/flask_openapi3/models/example.py index 833f2b42..a15ff481 100644 --- a/flask_openapi3/models/example.py +++ b/flask_openapi3/models/example.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:42 -from typing import Any, Optional +from typing import Any from pydantic import BaseModel @@ -11,9 +11,9 @@ class Example(BaseModel): https://spec.openapis.org/oas/v3.1.0#example-object """ - summary: Optional[str] = None - description: Optional[str] = None - value: Optional[Any] = None - externalValue: Optional[str] = None + summary: str | None = None + description: str | None = None + value: Any | None = None + externalValue: str | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/external_documentation.py b/flask_openapi3/models/external_documentation.py index 1ebb1c7f..14c1d53a 100644 --- a/flask_openapi3/models/external_documentation.py +++ b/flask_openapi3/models/external_documentation.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:43 -from typing import Optional from pydantic import BaseModel @@ -11,7 +10,7 @@ class ExternalDocumentation(BaseModel): https://spec.openapis.org/oas/v3.1.0#external-documentation-object """ - description: Optional[str] = None + description: str | None = None url: str model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/header.py b/flask_openapi3/models/header.py index de8316f5..418cb8de 100644 --- a/flask_openapi3/models/header.py +++ b/flask_openapi3/models/header.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:44 -from typing import Optional from .parameter import Parameter from .parameter_in_type import ParameterInType @@ -12,7 +11,7 @@ class Header(Parameter): https://spec.openapis.org/oas/v3.1.0#header-object """ - name: Optional[str] = None # type:ignore - param_in: Optional[ParameterInType] = None # type:ignore + name: str | None = None # type:ignore + param_in: ParameterInType | None = None # type:ignore model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/info.py b/flask_openapi3/models/info.py index 3f7a3dd7..ee45ff3e 100644 --- a/flask_openapi3/models/info.py +++ b/flask_openapi3/models/info.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2021/4/28 10:58 -from typing import Optional from pydantic import BaseModel @@ -15,11 +14,11 @@ class Info(BaseModel): """ title: str - summary: Optional[str] = None - description: Optional[str] = None - termsOfService: Optional[str] = None - contact: Optional[Contact] = None - license: Optional[License] = None + summary: str | None = None + description: str | None = None + termsOfService: str | None = None + contact: Contact | None = None + license: License | None = None version: str model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/license.py b/flask_openapi3/models/license.py index 58bb4390..652a8377 100644 --- a/flask_openapi3/models/license.py +++ b/flask_openapi3/models/license.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:45 -from typing import Optional from pydantic import BaseModel @@ -12,7 +11,7 @@ class License(BaseModel): """ name: str - identifier: Optional[str] = None - url: Optional[str] = None + identifier: str | None = None + url: str | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/link.py b/flask_openapi3/models/link.py index 69f8d2d2..c805bcb5 100644 --- a/flask_openapi3/models/link.py +++ b/flask_openapi3/models/link.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:45 -from typing import Any, Optional +from typing import Any from pydantic import BaseModel @@ -13,11 +13,11 @@ class Link(BaseModel): https://spec.openapis.org/oas/v3.1.0#link-object """ - operationRef: Optional[str] = None - operationId: Optional[str] = None - parameters: Optional[dict[str, Any]] = None - requestBody: Optional[Any] = None - description: Optional[str] = None - server: Optional[Server] = None + operationRef: str | None = None + operationId: str | None = None + parameters: dict[str, Any] | None = None + requestBody: Any | None = None + description: str | None = None + server: Server | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/media_type.py b/flask_openapi3/models/media_type.py index 5596b3f0..d356b88f 100644 --- a/flask_openapi3/models/media_type.py +++ b/flask_openapi3/models/media_type.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:46 -from typing import Any, Optional, Union +from typing import Any from pydantic import BaseModel, Field @@ -16,9 +16,9 @@ class MediaType(BaseModel): https://spec.openapis.org/oas/v3.1.0#media-type-object """ - media_type_schema: Optional[Union[Reference, Schema]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - encoding: Optional[dict[str, Encoding]] = None + media_type_schema: Reference | Schema | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None + encoding: dict[str, Encoding] | None = None model_config = {"extra": "allow", "populate_by_name": True} diff --git a/flask_openapi3/models/oauth_flow.py b/flask_openapi3/models/oauth_flow.py index 10946fad..3dd4cff1 100644 --- a/flask_openapi3/models/oauth_flow.py +++ b/flask_openapi3/models/oauth_flow.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:47 -from typing import Optional from pydantic import BaseModel @@ -11,9 +10,9 @@ class OAuthFlow(BaseModel): https://spec.openapis.org/oas/v3.1.0#oauth-flow-object """ - authorizationUrl: Optional[str] = None - tokenUrl: Optional[str] = None - refreshUrl: Optional[str] = None + authorizationUrl: str | None = None + tokenUrl: str | None = None + refreshUrl: str | None = None scopes: dict[str, str] model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/oauth_flows.py b/flask_openapi3/models/oauth_flows.py index f12d47f8..bce11825 100644 --- a/flask_openapi3/models/oauth_flows.py +++ b/flask_openapi3/models/oauth_flows.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:47 -from typing import Optional from pydantic import BaseModel @@ -13,9 +12,9 @@ class OAuthFlows(BaseModel): https://spec.openapis.org/oas/v3.1.0#oauth-flows-object """ - implicit: Optional[OAuthFlow] = None - password: Optional[OAuthFlow] = None - clientCredentials: Optional[OAuthFlow] = None - authorizationCode: Optional[OAuthFlow] = None + implicit: OAuthFlow | None = None + password: OAuthFlow | None = None + clientCredentials: OAuthFlow | None = None + authorizationCode: OAuthFlow | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/operation.py b/flask_openapi3/models/operation.py index 06a3be37..904be27e 100644 --- a/flask_openapi3/models/operation.py +++ b/flask_openapi3/models/operation.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:48 -from typing import Optional, Union from pydantic import BaseModel @@ -20,18 +19,18 @@ class Operation(BaseModel): https://spec.openapis.org/oas/v3.1.0#operation-object """ - tags: Optional[list[str]] = None - summary: Optional[str] = None - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None - operationId: Optional[str] = None - parameters: Optional[list[Parameter]] = None - requestBody: Optional[Union[RequestBody, Reference]] = None - responses: Optional[dict[str, Response]] = None - callbacks: Optional[dict[str, Callback]] = None + tags: list[str] | None = None + summary: str | None = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None + operationId: str | None = None + parameters: list[Parameter] | None = None + requestBody: RequestBody | Reference | None = None + responses: dict[str, Response] | None = None + callbacks: dict[str, Callback] | None = None - deprecated: Optional[bool] = False - security: Optional[list[SecurityRequirement]] = None - servers: Optional[list[Server]] = None + deprecated: bool | None = False + security: list[SecurityRequirement] | None = None + servers: list[Server] | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/parameter.py b/flask_openapi3/models/parameter.py index 9e3147d8..0c5a5463 100644 --- a/flask_openapi3/models/parameter.py +++ b/flask_openapi3/models/parameter.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:49 -from typing import Any, Optional, Union +from typing import Any from pydantic import BaseModel, Field @@ -19,16 +19,16 @@ class Parameter(BaseModel): name: str param_in: ParameterInType = Field(alias="in") - description: Optional[str] = None - required: Optional[bool] = None - deprecated: Optional[bool] = None - allowEmptyValue: Optional[bool] = None - style: Optional[str] = None - explode: Optional[bool] = None - allowReserved: Optional[bool] = None - param_schema: Optional[Union[Reference, Schema]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - content: Optional[dict[str, MediaType]] = None + description: str | None = None + required: bool | None = None + deprecated: bool | None = None + allowEmptyValue: bool | None = None + style: str | None = None + explode: bool | None = None + allowReserved: bool | None = None + param_schema: Reference | Schema | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None + content: dict[str, MediaType] | None = None model_config = {"extra": "allow", "populate_by_name": True} diff --git a/flask_openapi3/models/path_item.py b/flask_openapi3/models/path_item.py index f4d5ec7d..5622531b 100644 --- a/flask_openapi3/models/path_item.py +++ b/flask_openapi3/models/path_item.py @@ -2,7 +2,7 @@ # @Author : llc # @Time : 2023/7/4 9:50 import typing -from typing import Optional, Union +from typing import Optional from pydantic import BaseModel, Field @@ -19,9 +19,9 @@ class PathItem(BaseModel): https://spec.openapis.org/oas/v3.1.0#path-item-object """ - ref: Optional[str] = Field(default=None, alias="$ref") - summary: Optional[str] = None - description: Optional[str] = None + ref: str | None = Field(default=None, alias="$ref") + summary: str | None = None + description: str | None = None get: Optional["Operation"] = None put: Optional["Operation"] = None post: Optional["Operation"] = None @@ -30,7 +30,7 @@ class PathItem(BaseModel): head: Optional["Operation"] = None patch: Optional["Operation"] = None trace: Optional["Operation"] = None - servers: Optional[list[Server]] = None - parameters: Optional[list[Union[Parameter, Reference]]] = None + servers: list[Server] | None = None + parameters: list[Parameter | Reference] | None = None model_config = {"extra": "allow", "populate_by_name": True} diff --git a/flask_openapi3/models/request_body.py b/flask_openapi3/models/request_body.py index 054ade76..669b7948 100644 --- a/flask_openapi3/models/request_body.py +++ b/flask_openapi3/models/request_body.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:53 -from typing import Optional from pydantic import BaseModel @@ -13,8 +12,8 @@ class RequestBody(BaseModel): https://spec.openapis.org/oas/v3.1.0#request-body-object """ - description: Optional[str] = None + description: str | None = None content: dict[str, MediaType] - required: Optional[bool] = True + required: bool | None = True model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/response.py b/flask_openapi3/models/response.py index 7e2b8c0c..9ed93c20 100644 --- a/flask_openapi3/models/response.py +++ b/flask_openapi3/models/response.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:54 -from typing import Optional, Union from pydantic import BaseModel @@ -17,8 +16,8 @@ class Response(BaseModel): """ description: str - headers: Optional[dict[str, Union[Header, Reference]]] = None - content: Optional[dict[str, MediaType]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None + headers: dict[str, Header | Reference] | None = None + content: dict[str, MediaType] | None = None + links: dict[str, Link | Reference] | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/responses.py b/flask_openapi3/models/responses.py index 753a6f8e..ffb6b314 100644 --- a/flask_openapi3/models/responses.py +++ b/flask_openapi3/models/responses.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:55 -from typing import Union from .reference import Reference from .response import Response @@ -9,4 +8,4 @@ """ https://spec.openapis.org/oas/v3.1.0#responses-object """ -Responses = dict[str, Union[Response, Reference]] +Responses = dict[str, Response | Reference] diff --git a/flask_openapi3/models/schema.py b/flask_openapi3/models/schema.py index 068700f8..c02761aa 100644 --- a/flask_openapi3/models/schema.py +++ b/flask_openapi3/models/schema.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:55 -from typing import Any, Optional, Union +from typing import Any, Union from pydantic import BaseModel, Field @@ -17,42 +17,42 @@ class Schema(BaseModel): https://spec.openapis.org/oas/v3.1.0#schema-object """ - ref: Optional[str] = Field(alias="$ref", default=None) - title: Optional[str] = None - multipleOf: Optional[float] = Field(default=None, gt=0.0) - maximum: Optional[Union[int, float]] = None - exclusiveMaximum: Optional[float] = None - minimum: Optional[float] = None - exclusiveMinimum: Optional[float] = None - maxLength: Optional[int] = Field(default=None, ge=0) - minLength: Optional[int] = Field(default=None, ge=0) - pattern: Optional[str] = None - maxItems: Optional[int] = Field(default=None, ge=0) - minItems: Optional[int] = Field(default=None, ge=0) - uniqueItems: Optional[bool] = None - maxProperties: Optional[int] = Field(default=None, ge=0) - minProperties: Optional[int] = Field(default=None, ge=0) - required: Optional[list[str]] = Field(default=None) - enum: Union[None, list[Any]] = Field(default=None) - type: Optional[DataType] = Field(default=None) - allOf: Optional[list[Union[Reference, "Schema"]]] = None - oneOf: Optional[list[Union[Reference, "Schema"]]] = None - anyOf: Optional[list[Union[Reference, "Schema"]]] = None - schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not") - items: Optional[Union[Reference, "Schema"]] = None - properties: Optional[dict[str, Union[Reference, "Schema"]]] = None - prefixItems: Optional[list[Union[Reference, "Schema"]]] = None - additionalProperties: Optional[Union[bool, Reference, "Schema"]] = None - description: Optional[str] = None - schema_format: Optional[str] = Field(default=None, alias="format") - default: Optional[Any] = None - nullable: Optional[bool] = None - discriminator: Optional[Discriminator] = None - readOnly: Optional[bool] = None - writeOnly: Optional[bool] = None - xml: Optional[XML] = None - externalDocs: Optional[ExternalDocumentation] = None - example: Optional[Any] = None - deprecated: Optional[bool] = None + ref: str | None = Field(alias="$ref", default=None) + title: str | None = None + multipleOf: float | None = Field(default=None, gt=0.0) + maximum: int | float | None = None + exclusiveMaximum: float | None = None + minimum: float | None = None + exclusiveMinimum: float | None = None + maxLength: int | None = Field(default=None, ge=0) + minLength: int | None = Field(default=None, ge=0) + pattern: str | None = None + maxItems: int | None = Field(default=None, ge=0) + minItems: int | None = Field(default=None, ge=0) + uniqueItems: bool | None = None + maxProperties: int | None = Field(default=None, ge=0) + minProperties: int | None = Field(default=None, ge=0) + required: list[str] | None = Field(default=None) + enum: None | list[Any] = Field(default=None) + type: DataType | None = Field(default=None) + allOf: list[Union[Reference, "Schema"]] | None = None + oneOf: list[Union[Reference, "Schema"]] | None = None + anyOf: list[Union[Reference, "Schema"]] | None = None + schema_not: Union[Reference, "Schema"] | None = Field(default=None, alias="not") + items: Union[Reference, "Schema"] | None = None + properties: dict[str, Union[Reference, "Schema"]] | None = None + prefixItems: list[Union[Reference, "Schema"]] | None = None + additionalProperties: Union[bool, Reference, "Schema"] | None = None + description: str | None = None + schema_format: str | None = Field(default=None, alias="format") + default: Any | None = None + nullable: bool | None = None + discriminator: Discriminator | None = None + readOnly: bool | None = None + writeOnly: bool | None = None + xml: XML | None = None + externalDocs: ExternalDocumentation | None = None + example: Any | None = None + deprecated: bool | None = None model_config = {"populate_by_name": True} diff --git a/flask_openapi3/models/security_scheme.py b/flask_openapi3/models/security_scheme.py index 747f3f44..db02f372 100644 --- a/flask_openapi3/models/security_scheme.py +++ b/flask_openapi3/models/security_scheme.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:56 -from typing import Optional from pydantic import BaseModel, Field @@ -15,12 +14,12 @@ class SecurityScheme(BaseModel): """ type: str - description: Optional[str] = None - name: Optional[str] = None - security_scheme_in: Optional[SecuritySchemeInType] = Field(default=None, alias="in") - scheme: Optional[str] = None - bearerFormat: Optional[str] = None - flows: Optional[OAuthFlows] = None - openIdConnectUrl: Optional[str] = None + description: str | None = None + name: str | None = None + security_scheme_in: SecuritySchemeInType | None = Field(default=None, alias="in") + scheme: str | None = None + bearerFormat: str | None = None + flows: OAuthFlows | None = None + openIdConnectUrl: str | None = None model_config = {"extra": "allow", "populate_by_name": True} diff --git a/flask_openapi3/models/server.py b/flask_openapi3/models/server.py index 1996b7cc..d5d335ea 100644 --- a/flask_openapi3/models/server.py +++ b/flask_openapi3/models/server.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2021/4/28 11:26 -from typing import Optional from pydantic import BaseModel @@ -14,7 +13,7 @@ class Server(BaseModel): """ url: str - description: Optional[str] = None - variables: Optional[dict[str, ServerVariable]] = None + description: str | None = None + variables: dict[str, ServerVariable] | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/server_variable.py b/flask_openapi3/models/server_variable.py index 812e32a9..7c28c158 100644 --- a/flask_openapi3/models/server_variable.py +++ b/flask_openapi3/models/server_variable.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:57 -from typing import Optional from pydantic import BaseModel, Field @@ -11,8 +10,8 @@ class ServerVariable(BaseModel): https://spec.openapis.org/oas/v3.1.0#server-variable-object """ - enum: Optional[list[str]] = Field(None, min_length=1) + enum: list[str] | None = Field(None, min_length=1) default: str - description: Optional[str] = None + description: str | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/tag.py b/flask_openapi3/models/tag.py index b02588e5..9282b082 100644 --- a/flask_openapi3/models/tag.py +++ b/flask_openapi3/models/tag.py @@ -1,5 +1,3 @@ -from typing import Optional - from pydantic import BaseModel from .external_documentation import ExternalDocumentation @@ -11,7 +9,7 @@ class Tag(BaseModel): """ name: str - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None model_config = {"extra": "allow"} diff --git a/flask_openapi3/models/validation_error.py b/flask_openapi3/models/validation_error.py index 961788ff..0589ef6d 100644 --- a/flask_openapi3/models/validation_error.py +++ b/flask_openapi3/models/validation_error.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2021/5/10 14:51 -from typing import Any, Optional +from typing import Any from pydantic import BaseModel, Field @@ -12,8 +12,8 @@ class ValidationErrorModel(BaseModel): loc: list[Any] = Field(..., title="Location", description="The error's location as a list.") msg: str = Field(..., title="Message", description="A human readable explanation of the error.") input: Any = Field(..., title="Input", description="The input provided for validation.") - url: Optional[str] = Field(None, title="URL", description="The URL to further information about the error.") - ctx: Optional[dict[str, Any]] = Field( + url: str | None = Field(None, title="URL", description="The URL to further information about the error.") + ctx: dict[str, Any] | None = Field( None, title="Error context", description="An optional object which contains values required to render the error message.", diff --git a/flask_openapi3/models/xml.py b/flask_openapi3/models/xml.py index 0af900e1..0bda6e7b 100644 --- a/flask_openapi3/models/xml.py +++ b/flask_openapi3/models/xml.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2023/7/4 9:58 -from typing import Optional from pydantic import BaseModel @@ -11,9 +10,9 @@ class XML(BaseModel): https://spec.openapis.org/oas/v3.1.0#xml-object """ - name: Optional[str] = None - namespace: Optional[str] = None - prefix: Optional[str] = None + name: str | None = None + namespace: str | None = None + prefix: str | None = None attribute: bool = False wrapped: bool = False diff --git a/flask_openapi3/openapi.py b/flask_openapi3/openapi.py index b8dd711b..c9c04e2b 100644 --- a/flask_openapi3/openapi.py +++ b/flask_openapi3/openapi.py @@ -3,18 +3,13 @@ # @Time : 2021/4/30 14:25 import os import re -import sys from importlib import import_module -from typing import Any, Callable, Optional, Type, Union +from importlib.metadata import entry_points +from typing import Any, Callable, Type from flask import Blueprint, Flask, render_template_string from pydantic import BaseModel -if sys.version_info >= (3, 10): - from importlib.metadata import entry_points -else: # pragma: no cover - from importlib_metadata import entry_points # type: ignore - from .blueprint import APIBlueprint from .commands import openapi_command from .models import ( @@ -52,14 +47,14 @@ def __init__( self, import_name: str, *, - info: Optional[Info] = None, - security_schemes: Optional[SecuritySchemesDict] = None, - responses: Optional[ResponseDict] = None, - servers: Optional[list[Server]] = None, - external_docs: Optional[ExternalDocumentation] = None, + info: Info | None = None, + security_schemes: SecuritySchemesDict | None = None, + responses: ResponseDict | None = None, + servers: list[Server] | None = None, + external_docs: ExternalDocumentation | None = None, operation_id_callback: Callable = get_operation_id_for_path, - openapi_extensions: Optional[dict[str, Any]] = None, - validation_error_status: Union[str, int] = 422, + openapi_extensions: dict[str, Any] | None = None, + validation_error_status: str | int = 422, validation_error_model: Type[BaseModel] = ValidationErrorModel, validation_error_callback: Callable = make_validation_error_response, doc_ui: bool = True, @@ -313,7 +308,7 @@ def register_api(self, api: APIBlueprint, **options: Any) -> None: self.register_blueprint(api, **options) def register_api_view( - self, api_view: APIView, url_prefix: Optional[str] = None, view_kwargs: Optional[dict[Any, Any]] = None + self, api_view: APIView, url_prefix: str | None = None, view_kwargs: dict[Any, Any] | None = None ) -> None: """ Register APIView @@ -365,16 +360,16 @@ def _collect_openapi_info( rule: str, func: Callable, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, method: str = HTTPMethod.GET, ) -> ParametersTuple: diff --git a/flask_openapi3/request.py b/flask_openapi3/request.py index a6e83add..2c837bcf 100644 --- a/flask_openapi3/request.py +++ b/flask_openapi3/request.py @@ -5,7 +5,7 @@ import json from functools import wraps from json import JSONDecodeError -from typing import Any, Optional, Type +from typing import Any, Type from flask import abort, current_app, request from pydantic import BaseModel, ValidationError @@ -155,14 +155,14 @@ def _validate_body(body: Type[BaseModel], func_kwargs: dict): def _validate_request( - header: Optional[Type[BaseModel]] = None, - cookie: Optional[Type[BaseModel]] = None, - path: Optional[Type[BaseModel]] = None, - query: Optional[Type[BaseModel]] = None, - form: Optional[Type[BaseModel]] = None, - body: Optional[Type[BaseModel]] = None, - raw: Optional[Type[BaseModel]] = None, - path_kwargs: Optional[dict[Any, Any]] = None, + header: Type[BaseModel] | None = None, + cookie: Type[BaseModel] | None = None, + path: Type[BaseModel] | None = None, + query: Type[BaseModel] | None = None, + form: Type[BaseModel] | None = None, + body: Type[BaseModel] | None = None, + raw: Type[BaseModel] | None = None, + path_kwargs: dict[Any, Any] | None = None, ) -> dict: """ Validate requests and responses. diff --git a/flask_openapi3/scaffold.py b/flask_openapi3/scaffold.py index 309ad491..02210445 100644 --- a/flask_openapi3/scaffold.py +++ b/flask_openapi3/scaffold.py @@ -3,7 +3,7 @@ # @Time : 2022/8/30 9:40 import inspect from functools import wraps -from typing import Any, Callable, Optional +from typing import Any, Callable from flask.wrappers import Response as FlaskResponse @@ -19,16 +19,16 @@ def _collect_openapi_info( rule: str, func: Callable, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, method: str = HTTPMethod.GET, ) -> ParametersTuple: @@ -120,16 +120,16 @@ def get( self, rule: str, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, **options: Any, ) -> Callable: @@ -182,16 +182,16 @@ def post( self, rule: str, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, **options: Any, ) -> Callable: @@ -244,16 +244,16 @@ def put( self, rule: str, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, **options: Any, ) -> Callable: @@ -306,16 +306,16 @@ def delete( self, rule: str, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, **options: Any, ) -> Callable: @@ -368,16 +368,16 @@ def patch( self, rule: str, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, **options: Any, ) -> Callable: diff --git a/flask_openapi3/types.py b/flask_openapi3/types.py index 4b1f2cc8..22154a23 100644 --- a/flask_openapi3/types.py +++ b/flask_openapi3/types.py @@ -2,26 +2,26 @@ # @Author : llc # @Time : 2023/7/9 15:25 from http import HTTPStatus -from typing import Any, Optional, Type, Union +from typing import Any, Type from pydantic import BaseModel from .models import RawModel, SecurityScheme -_ResponseDictValue = Union[Type[BaseModel], dict[Any, Any], None] +_ResponseDictValue = Type[BaseModel] | dict[Any, Any] | None -ResponseDict = dict[Union[str, int, HTTPStatus], _ResponseDictValue] +ResponseDict = dict[str | int | HTTPStatus, _ResponseDictValue] ResponseStrKeyDict = dict[str, _ResponseDictValue] -SecuritySchemesDict = dict[str, Union[SecurityScheme, dict[str, Any]]] +SecuritySchemesDict = dict[str, SecurityScheme | dict[str, Any]] ParametersTuple = tuple[ - Optional[Type[BaseModel]], - Optional[Type[BaseModel]], - Optional[Type[BaseModel]], - Optional[Type[BaseModel]], - Optional[Type[BaseModel]], - Optional[Type[BaseModel]], - Optional[Type[RawModel]], + Type[BaseModel] | None, + Type[BaseModel] | None, + Type[BaseModel] | None, + Type[BaseModel] | None, + Type[BaseModel] | None, + Type[BaseModel] | None, + Type[RawModel] | None, ] diff --git a/flask_openapi3/utils.py b/flask_openapi3/utils.py index aa5aa9c9..32ae175d 100644 --- a/flask_openapi3/utils.py +++ b/flask_openapi3/utils.py @@ -7,7 +7,7 @@ import sys from enum import Enum from http import HTTPStatus -from typing import Any, Callable, DefaultDict, Optional, Type, get_type_hints +from typing import Any, Callable, DefaultDict, Type, get_type_hints from flask import current_app, make_response from flask.wrappers import Response as FlaskResponse @@ -53,9 +53,9 @@ class HTTPMethod(str, Enum): def get_operation( func: Callable, *, - summary: Optional[str] = None, - description: Optional[str] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + summary: str | None = None, + description: str | None = None, + openapi_extensions: dict[str, Any] | None = None, ) -> Operation: """ Return an Operation object with the specified summary and description. @@ -395,8 +395,8 @@ def parse_and_store_tags( def parse_parameters( func: Callable, *, - components_schemas: Optional[dict] = None, - operation: Optional[Operation] = None, + components_schemas: dict | None = None, + operation: Operation | None = None, doc_ui: bool = True, ) -> ParametersTuple: """ @@ -427,13 +427,13 @@ def parse_parameters( annotations = get_type_hints(func) # Get the types for header, cookie, path, query, form, and body parameters - header: Optional[Type[BaseModel]] = annotations.get("header") - cookie: Optional[Type[BaseModel]] = annotations.get("cookie") - path: Optional[Type[BaseModel]] = annotations.get("path") - query: Optional[Type[BaseModel]] = annotations.get("query") - form: Optional[Type[BaseModel]] = annotations.get("form") - body: Optional[Type[BaseModel]] = annotations.get("body") - raw: Optional[Type[RawModel]] = annotations.get("raw") + header: Type[BaseModel] | None = annotations.get("header") + cookie: Type[BaseModel] | None = annotations.get("cookie") + path: Type[BaseModel] | None = annotations.get("path") + query: Type[BaseModel] | None = annotations.get("query") + form: Type[BaseModel] | None = annotations.get("form") + body: Type[BaseModel] | None = annotations.get("body") + raw: Type[RawModel] | None = annotations.get("raw") # If doc_ui is False, return the types without further processing if doc_ui is False: diff --git a/flask_openapi3/view.py b/flask_openapi3/view.py index abefd795..c469a161 100644 --- a/flask_openapi3/view.py +++ b/flask_openapi3/view.py @@ -2,7 +2,7 @@ # @Author : llc # @Time : 2022/10/14 16:09 import typing -from typing import Any, Callable, Optional +from typing import Any, Callable from .models import ExternalDocumentation, Server, Tag from .types import ResponseDict @@ -25,10 +25,10 @@ class APIView: def __init__( self, - url_prefix: Optional[str] = None, - view_tags: Optional[list[Tag]] = None, - view_security: Optional[list[dict[str, list[str]]]] = None, - view_responses: Optional[ResponseDict] = None, + url_prefix: str | None = None, + view_tags: list[Tag] | None = None, + view_security: list[dict[str, list[str]]] | None = None, + view_responses: ResponseDict | None = None, doc_ui: bool = True, operation_id_callback: Callable = get_operation_id_for_path, ): @@ -100,16 +100,16 @@ def wrapper(cls): def doc( self, *, - tags: Optional[list[Tag]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - external_docs: Optional[ExternalDocumentation] = None, - operation_id: Optional[str] = None, - responses: Optional[ResponseDict] = None, - deprecated: Optional[bool] = None, - security: Optional[list[dict[str, list[Any]]]] = None, - servers: Optional[list[Server]] = None, - openapi_extensions: Optional[dict[str, Any]] = None, + tags: list[Tag] | None = None, + summary: str | None = None, + description: str | None = None, + external_docs: ExternalDocumentation | None = None, + operation_id: str | None = None, + responses: ResponseDict | None = None, + deprecated: bool | None = None, + security: list[dict[str, list[Any]]] | None = None, + servers: list[Server] | None = None, + openapi_extensions: dict[str, Any] | None = None, doc_ui: bool = True, ) -> Callable: """ @@ -182,7 +182,7 @@ def decorator(func): return decorator def register( - self, app: "OpenAPI", url_prefix: Optional[str] = None, view_kwargs: Optional[dict[Any, Any]] = None + self, app: "OpenAPI", url_prefix: str | None = None, view_kwargs: dict[Any, Any] | None = None ) -> None: """ Register the API views with the given OpenAPI app. diff --git a/pyproject.toml b/pyproject.toml index 9b487075..502ddf53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,12 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = ["Flask>=2.0", "pydantic>=2.4"] dynamic = ["version"] @@ -76,7 +75,8 @@ select = [ "E7", # Statement issues "E9", # Runtime errors "F", # Pyflakes rules - "Q" # flake8-quotes + "Q", # flake8-quotes + "UP045", # Use X | None for type annotations ] [tool.ruff.lint.per-file-ignores] diff --git a/tests/test_api_blueprint.py b/tests/test_api_blueprint.py index 7d93dcc8..8e1ee4c1 100644 --- a/tests/test_api_blueprint.py +++ b/tests/test_api_blueprint.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2021/5/17 15:25 -from typing import Optional import pytest from pydantic import BaseModel, Field @@ -56,7 +55,7 @@ def client(): class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") @@ -143,7 +142,7 @@ def test_delete(client): class AuthorBody(BaseModel): - age: Optional[int] = Field(..., ge=1, le=100, description="Age") + age: int | None = Field(..., ge=1, le=100, description="Age") @author_api.post("/") diff --git a/tests/test_api_view.py b/tests/test_api_view.py index 0fa1fb8b..f2ef4245 100644 --- a/tests/test_api_view.py +++ b/tests/test_api_view.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2022/11/4 14:41 -from typing import Optional import pytest from pydantic import BaseModel, Field @@ -28,11 +27,11 @@ class BookPath(BaseModel): class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/tests/test_async.py b/tests/test_async.py index 1fe9a791..a5ff2639 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -2,7 +2,6 @@ # @Author : llc # @Time : 2022/12/5 10:27 -from typing import Optional import pytest from pydantic import BaseModel, Field @@ -19,11 +18,11 @@ class Query(BaseModel): class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/tests/test_form.py b/tests/test_form.py index c2e00adf..400377b8 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -2,7 +2,7 @@ # @Author : llc # @Time : 2023/8/6 13:47 from enum import Enum -from typing import Any, Union +from typing import Any import pytest from pydantic import BaseModel @@ -49,9 +49,9 @@ class FormParameters(BaseModel): parameter: MetadataParameter parameter_dict: dict[str, MetadataParameter] parameter_list: list[MetadataParameter] - parameter_list_union: list[Union[bool, float, str, int, FileType, MetadataParameter]] - parameter_union: Union[MetadataParameter, MetadataParameter2] - union_all: Union[str, int, float, bool, FileType, MetadataParameter] + parameter_list_union: list[bool | float | str | int | FileType | MetadataParameter] + parameter_union: MetadataParameter | MetadataParameter2 + union_all: str | int | float | bool | FileType | MetadataParameter none: None = None default_value: str = "default_value" diff --git a/tests/test_model_extra.py b/tests/test_model_extra.py index d2a155a4..a8ec5e9d 100644 --- a/tests/test_model_extra.py +++ b/tests/test_model_extra.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # @Author : llc # @Time : 2024/11/20 14:45 -from typing import Optional import pytest from pydantic import BaseModel, ConfigDict, Field @@ -13,7 +12,7 @@ class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") model_config = ConfigDict(extra="allow") diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 4d9d38b7..46e01d39 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Generic, Literal, Optional, TypeVar +from typing import Generic, Literal, TypeVar from pydantic import BaseModel, Field @@ -407,8 +407,8 @@ def endpoint_test(header: HeaderParam, cookie: CookieParam): class Model(BaseModel): - one: Optional[int] = Field(default=None) - two: Optional[int] = Field(default=2) + one: int | None = Field(default=None) + two: int | None = Field(default=2) def test_default_none(request): diff --git a/tests/test_request.py b/tests/test_request.py index 2f4a1129..dc2975d4 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -3,7 +3,6 @@ # @Time : 2022/9/2 15:35 from enum import Enum from functools import wraps -from typing import Optional import pytest from pydantic import BaseModel, Field @@ -35,7 +34,7 @@ class BookForm(BaseModel): class BookQuery(BaseModel): age: list[int] - book_type: Optional[TypeEnum] = None + book_type: TypeEnum | None = None class BookBody(BaseModel): @@ -43,8 +42,8 @@ class BookBody(BaseModel): class BookCookie(BaseModel): - token: Optional[str] = None - token_type: Optional[TypeEnum] = None + token: str | None = None + token_type: TypeEnum | None = None class BookHeader(BaseModel): @@ -52,7 +51,7 @@ class BookHeader(BaseModel): # required hello2: str = Field(..., max_length=12, description="sds") api_key: str = Field(..., description="API Key") - api_type: Optional[TypeEnum] = None + api_type: TypeEnum | None = None x_hello: str = Field(..., max_length=12, description="Header with alias to support dash", alias="x-hello") diff --git a/tests/test_restapi.py b/tests/test_restapi.py index ffb995fd..24e02ece 100644 --- a/tests/test_restapi.py +++ b/tests/test_restapi.py @@ -5,7 +5,6 @@ import json from http import HTTPStatus -from typing import Optional import pytest from flask import Response @@ -43,11 +42,11 @@ def get_operation_id_for_path_callback(*, name: str, path: str, method: str) -> class BookQuery(BaseModel): - age: Optional[int] = Field(None, description="Age") + age: int | None = Field(None, description="Age") class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") @@ -57,7 +56,7 @@ class BookPath(BaseModel): class BookBodyWithID(BaseModel): bid: int = Field(..., description="book id") - age: Optional[int] = Field(None, ge=2, le=4, description="Age") + age: int | None = Field(None, ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") diff --git a/tests/test_validate_request.py b/tests/test_validate_request.py index 3906601e..2f44b2d2 100644 --- a/tests/test_validate_request.py +++ b/tests/test_validate_request.py @@ -1,5 +1,4 @@ from functools import wraps -from typing import Optional import pytest from flask import request @@ -14,7 +13,7 @@ class BookNamePath(BaseModel): class BookBody(BaseModel): - age: Optional[int] = Field(..., ge=2, le=4, description="Age") + age: int | None = Field(..., ge=2, le=4, description="Age") author: str = Field(None, min_length=2, max_length=4, description="Author") name: str From a64533d63839b2cba74f1c6e085f03ded6dedee3 Mon Sep 17 00:00:00 2001 From: luolingchun Date: Sat, 9 Aug 2025 13:05:34 +0800 Subject: [PATCH 2/2] Support for Python 3.14 --- .github/workflows/tests.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 86dbbc8f..3c2dd571 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [ "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] flask-version: [ "Flask>=2.0,<3.0", "Flask>=3.0" ] env: PYTHONPATH: . diff --git a/pyproject.toml b/pyproject.toml index 502ddf53..274c5394 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] requires-python = ">=3.10" dependencies = ["Flask>=2.0", "pydantic>=2.4"]