From 825f322541c5ba9da400b4f546adf6fbc493ebae Mon Sep 17 00:00:00 2001 From: tarsil Date: Mon, 3 Jul 2023 13:22:39 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=AA=9B=20Initial=20refactor=20to=20py?= =?UTF-8?q?dantic=202.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/1-issue.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/test-suite.yml | 4 +- .pre-commit-config.yaml | 111 ++++++------------ docs/contributing.md | 2 +- mkdocs.yml | 2 +- openapi_schemas_pydantic/utils/constants.py | 4 +- openapi_schemas_pydantic/utils/utils.py | 6 +- openapi_schemas_pydantic/v3_1_0/__init__.py | 4 +- openapi_schemas_pydantic/v3_1_0/components.py | 71 +++++------ openapi_schemas_pydantic/v3_1_0/contact.py | 46 ++------ .../v3_1_0/discriminator.py | 31 ++--- openapi_schemas_pydantic/v3_1_0/encoding.py | 35 +++--- openapi_schemas_pydantic/v3_1_0/example.py | 35 +++--- .../v3_1_0/external_documentation.py | 15 +-- openapi_schemas_pydantic/v3_1_0/header.py | 18 +-- openapi_schemas_pydantic/v3_1_0/info.py | 49 ++++---- openapi_schemas_pydantic/v3_1_0/license.py | 21 ++-- openapi_schemas_pydantic/v3_1_0/link.py | 11 +- openapi_schemas_pydantic/v3_1_0/media_type.py | 13 +- openapi_schemas_pydantic/v3_1_0/oauth_flow.py | 11 +- .../v3_1_0/oauth_flows.py | 5 +- openapi_schemas_pydantic/v3_1_0/open_api.py | 5 +- openapi_schemas_pydantic/v3_1_0/operation.py | 12 +- openapi_schemas_pydantic/v3_1_0/parameter.py | 14 +-- openapi_schemas_pydantic/v3_1_0/path_item.py | 14 +-- openapi_schemas_pydantic/v3_1_0/reference.py | 14 +-- .../v3_1_0/request_body.py | 12 +- openapi_schemas_pydantic/v3_1_0/response.py | 12 +- openapi_schemas_pydantic/v3_1_0/schema.py | 16 +-- .../v3_1_0/security_scheme.py | 14 +-- openapi_schemas_pydantic/v3_1_0/server.py | 12 +- .../v3_1_0/server_variable.py | 6 +- openapi_schemas_pydantic/v3_1_0/tag.py | 10 +- openapi_schemas_pydantic/v3_1_0/xml.py | 12 +- pyproject.toml | 60 +++++++--- scripts/clean | 9 +- scripts/format | 16 --- scripts/lint | 15 +-- scripts/test | 4 +- tests/v3_1_0/test_alias.py | 28 ++--- tests/v3_1_0/test_config_example.py | 4 +- tests/v3_1_0/test_empty_schema.py | 2 +- tests/v3_1_0/test_example.py | 6 +- tests/v3_1_0/test_schema.py | 2 +- tests/v3_1_0/test_security_scheme.py | 6 +- tests/v3_1_0/test_swagger_openapi_v3.py | 5 +- tests/v3_1_0/test_util.py | 2 +- 48 files changed, 381 insertions(+), 429 deletions(-) delete mode 100755 scripts/format diff --git a/.github/ISSUE_TEMPLATE/1-issue.md b/.github/ISSUE_TEMPLATE/1-issue.md index 163ab63..dcb92f4 100644 --- a/.github/ISSUE_TEMPLATE/1-issue.md +++ b/.github/ISSUE_TEMPLATE/1-issue.md @@ -5,7 +5,7 @@ about: Please only raise an issue if you've been advised to do so after discussi The starting point for issues should usually be a discussion... -https://github.com/dymmond/esmerald/discussions +https://github.com/dymmond/openapi-schemas-pydantic/discussions Potential bugs may be raised as a "Potential Issue" discussion. The feature requests may be raised as an "Ideas" discussion. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2a2c0de..d95c888 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,6 +2,6 @@ blank_issues_enabled: false contact_links: - name: Discussions - url: https://github.com/dymmond/esmerald/discussions + url: https://github.com/dymmond/openapi-schemas-pydantic/discussions about: > The "Discussions" forum is where you want to start. diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 1c16b94..43d1266 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -31,8 +31,8 @@ jobs: - name: "Install dependencies" if: steps.cache.outputs.cache-hit != 'true' run: "scripts/install" - # - name: "Run linting checks" - # run: "scripts/lint" + - name: "Run linting checks" + run: "scripts/lint" # - name: "Build package" # run: "scripts/build" - name: "Run tests" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc310ac..690dea0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,90 +1,49 @@ +# See https://pre-commit.com for more information. +# See https://pre-commit.com/hooks.html for more hooks. +default_language_version: + python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - - id: check-ast - - id: check-case-conflict - - id: check-merge-conflict - - id: debug-statements + - id: check-added-large-files + - id: check-toml + - id: check-yaml + args: + - --unsafe - id: end-of-file-fixer - exclude: "\\.idea/(.)*" - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v2.38.0 + rev: v2.37.3 hooks: - id: pyupgrade - args: ["--py37-plus"] - - repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort + args: + - --py3-plus + - --keep-runtime-typing + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.254 + hooks: + - id: ruff + args: ["--fix", "--line-length=99"] - repo: https://github.com/psf/black rev: 22.8.0 hooks: - id: black - args: [--config=./pyproject.toml] - - repo: https://github.com/codespell-project/codespell - rev: v2.2.1 - hooks: - - id: codespell - - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 - hooks: - - id: blacken-docs - - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.0-alpha.0" - hooks: - - id: prettier - - repo: https://github.com/pycqa/bandit - rev: 1.7.4 - hooks: - - id: bandit - exclude: "test_*" - args: ["-iii", "-ll", "-s=B308,B703"] - - repo: https://github.com/hadialqattan/pycln - rev: v2.1.1 - hooks: - - id: pycln - args: [--config=pyproject.toml] - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.32.2 - hooks: - - id: markdownlint - args: [--disable=MD013, --disable=MD033, --disable=MD050] - - repo: https://github.com/PyCQA/docformatter - rev: v1.5.0 - hooks: - - id: docformatter - args: [--in-place] - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - additional_dependencies: - [ - "flake8-bugbear", - "flake8-comprehensions", - "flake8-mutable", - "flake8-print", - "flake8-simplify", - "flake8-type-checking", - "flake8-pytest-style", - "flake8-implicit-str-concat", - "flake8-noqa", - ] - - repo: https://github.com/johnfraney/flake8-markdown - rev: v0.4.0 - hooks: - - id: flake8-markdown - - repo: https://github.com/pycqa/pylint - rev: "v2.15.2" - hooks: - - id: pylint - exclude: "test_*|docs" - args: ["--unsafe-load-any-extension=y"] - additional_dependencies: [pydantic, mkdocs_gen_files] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.971" + args: ["--line-length=99"] + - repo: https://github.com/pycqa/isort + rev: 5.12.0 hooks: - - id: mypy - additional_dependencies: [pydantic, mkdocs_gen_files] + - id: isort + name: isort (python) + args: ["--project=openapi_schemas_pydantic", "--line-length=99"] + - id: isort + name: isort (cython) + types: [cython] + args: ["--project=openapi_schemas_pydantic", "--line-length=99"] + - id: isort + name: isort (pyi) + types: [pyi] + args: ["--project=openapi_schemas_pydantic", "--line-length=99"] +ci: + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/docs/contributing.md b/docs/contributing.md index 048929a..6fcdd43 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -42,7 +42,7 @@ To develop for OpenAPI Schemas, create a fork of the [OpenAPI Schemas Pydantic r After, clone your fork with the follow command replacing `YOUR-USERNAME` wih your GitHub username: ```shell -$ git clone https://github.com/YOUR-USERNAME/esmerald +$ git clone https://github.com/YOUR-USERNAME/openapi_schemas_pydantic ``` ### Install the project dependencies diff --git a/mkdocs.yml b/mkdocs.yml index ada20f3..175ee62 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,7 +28,7 @@ theme: - search.highlight - content.tabs.link -repo_name: dymmond/esmerald +repo_name: dymmond/openapi-schemas-pydantic repo_url: https://github.com/dymmond/openapi-schemas-pydantic edit_uri: "" plugins: diff --git a/openapi_schemas_pydantic/utils/constants.py b/openapi_schemas_pydantic/utils/constants.py index f08b757..8b9e439 100644 --- a/openapi_schemas_pydantic/utils/constants.py +++ b/openapi_schemas_pydantic/utils/constants.py @@ -216,9 +216,7 @@ "gt": "exclusiveMinimum", "max_length": "maxLength", "min_length": "minLength", - "max_items": "maxItems", - "min_items": "minItems", - "regex": "pattern", + "pattern": "pattern", "title": "title", "description": "description", } diff --git a/openapi_schemas_pydantic/utils/utils.py b/openapi_schemas_pydantic/utils/utils.py index 8b77f5f..bdd38b8 100644 --- a/openapi_schemas_pydantic/utils/utils.py +++ b/openapi_schemas_pydantic/utils/utils.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Any, Set, Type, TypeVar, cast from pydantic import BaseModel, create_model -from pydantic.schema import schema +from pydantic.json_schema import models_json_schema from openapi_schemas_pydantic import v3_1_0 @@ -54,10 +54,10 @@ def construct_open_api_with_schema_class( for cls in schema_classes ] schema_classes.sort(key=lambda x: x.__name__) - schema_definitions = schema(schema_classes, ref_prefix=REF_PREFIX)["definitions"] + schema_definitions = models_json_schema(schema_classes, ref_template=REF_PREFIX)["definitions"] copied_schema.components.schemas.update( { - key: v3_1_0.Schema.parse_obj(schema_dict) + key: v3_1_0.Schema.model_validate(schema_dict) for key, schema_dict in schema_definitions.items() } ) diff --git a/openapi_schemas_pydantic/v3_1_0/__init__.py b/openapi_schemas_pydantic/v3_1_0/__init__.py index 0802015..bbfcb39 100644 --- a/openapi_schemas_pydantic/v3_1_0/__init__.py +++ b/openapi_schemas_pydantic/v3_1_0/__init__.py @@ -37,8 +37,8 @@ from .xml import XML # resolve forward references -Encoding.update_forward_refs(Header=Header) -Schema.update_forward_refs() +Encoding.model_rebuild() +Schema.model_rebuild() __all__ = [ "Callback", diff --git a/openapi_schemas_pydantic/v3_1_0/components.py b/openapi_schemas_pydantic/v3_1_0/components.py index 8a69001..92f4c64 100644 --- a/openapi_schemas_pydantic/v3_1_0/components.py +++ b/openapi_schemas_pydantic/v3_1_0/components.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict from .callback import Callback from .example import Example @@ -23,39 +23,9 @@ class Components(BaseModel): outside the components object. """ - schemas: Optional[Dict[str, Schema]] = None - """An object to hold reusable [Schema Objects](https://spec.openapis.org/oas/v3.1.0#schemaObject).""" - - responses: Optional[Dict[str, Union[Response, Reference]]] = None - """An object to hold reusable [Response Objects](https://spec.openapis.org/oas/v3.1.0#responseObject).""" - - parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None - """An object to hold reusable [Parameter Objects](https://spec.openapis.org/oas/v3.1.0#parameterObject).""" - - examples: Optional[Dict[str, Union[Example, Reference]]] = None - """An object to hold reusable [Example Objects](https://spec.openapis.org/oas/v3.1.0#exampleObject).""" - - requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None - """An object to hold reusable [Request Body Objects](https://spec.openapis.org/oas/v3.1.0#requestBodyObject).""" - - headers: Optional[Dict[str, Union[Header, Reference]]] = None - """An object to hold reusable [Header Objects](https://spec.openapis.org/oas/v3.1.0#headerObject).""" - - securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None - """An object to hold reusable [Security Scheme Objects](https://spec.openapis.org/oas/v3.1.0#securitySchemeObject).""" - - links: Optional[Dict[str, Union[Link, Reference]]] = None - """An object to hold reusable [Link Objects](https://spec.openapis.org/oas/v3.1.0#linkObject).""" - - callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None - """An object to hold reusable [Callback Objects](https://spec.openapis.org/oas/v3.1.0#callbackObject).""" - - pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None - """An object to hold reusable [Path Item Object](https://spec.openapis.org/oas/v3.1.0#pathItemObject).""" - - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ "examples": [ { "schemas": { @@ -126,4 +96,35 @@ class Config: }, } ] - } + }, + ) + + schemas: Optional[Dict[str, Schema]] = None + """An object to hold reusable [Schema Objects](https://spec.openapis.org/oas/v3.1.0#schemaObject).""" + + responses: Optional[Dict[str, Union[Response, Reference]]] = None + """An object to hold reusable [Response Objects](https://spec.openapis.org/oas/v3.1.0#responseObject).""" + + parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None + """An object to hold reusable [Parameter Objects](https://spec.openapis.org/oas/v3.1.0#parameterObject).""" + + examples: Optional[Dict[str, Union[Example, Reference]]] = None + """An object to hold reusable [Example Objects](https://spec.openapis.org/oas/v3.1.0#exampleObject).""" + + requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None + """An object to hold reusable [Request Body Objects](https://spec.openapis.org/oas/v3.1.0#requestBodyObject).""" + + headers: Optional[Dict[str, Union[Header, Reference]]] = None + """An object to hold reusable [Header Objects](https://spec.openapis.org/oas/v3.1.0#headerObject).""" + + securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None + """An object to hold reusable [Security Scheme Objects](https://spec.openapis.org/oas/v3.1.0#securitySchemeObject).""" + + links: Optional[Dict[str, Union[Link, Reference]]] = None + """An object to hold reusable [Link Objects](https://spec.openapis.org/oas/v3.1.0#linkObject).""" + + callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None + """An object to hold reusable [Callback Objects](https://spec.openapis.org/oas/v3.1.0#callbackObject).""" + + pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None + """An object to hold reusable [Path Item Object](https://spec.openapis.org/oas/v3.1.0#pathItemObject).""" diff --git a/openapi_schemas_pydantic/v3_1_0/contact.py b/openapi_schemas_pydantic/v3_1_0/contact.py index 8b250df..e466a9d 100644 --- a/openapi_schemas_pydantic/v3_1_0/contact.py +++ b/openapi_schemas_pydantic/v3_1_0/contact.py @@ -1,9 +1,21 @@ from typing import Optional, Union -from pydantic import AnyUrl, BaseModel, EmailStr, Extra, validator +from pydantic import AnyUrl, BaseModel, ConfigDict, EmailStr class Contact(BaseModel): + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "examples": [ + { + "name": "API Support", + "url": "http://www.example.com/support", + "email": "support@example.com", + } + ] + }, + ) """Contact information for the exposed API.""" name: Optional[str] = None @@ -22,35 +34,3 @@ class Contact(BaseModel): The email address of the contact person/organization. MUST be in the form of an email address. """ - - @validator("email", pre=True) - def validate_email( # pylint: disable=no-self-argument - cls, - v: Union[EmailStr, str], - ) -> EmailStr: - """Validates that email is a valid email address. - - Args: - v: Holds the email string to be validated - - Raises: - ValueError: Value is not a valid email address - - Returns: - Validated email string. - """ - if isinstance(v, str): - v = EmailStr(v) - return v - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [ - { - "name": "API Support", - "url": "http://www.example.com/support", - "email": "support@example.com", - } - ] - } diff --git a/openapi_schemas_pydantic/v3_1_0/discriminator.py b/openapi_schemas_pydantic/v3_1_0/discriminator.py index 8ae9774..c1c4fbf 100644 --- a/openapi_schemas_pydantic/v3_1_0/discriminator.py +++ b/openapi_schemas_pydantic/v3_1_0/discriminator.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict class Discriminator(BaseModel): @@ -14,19 +14,9 @@ class Discriminator(BaseModel): When using the discriminator, _inline_ schemas will not be considered. """ - propertyName: str - """ - **REQUIRED**. The name of the property in the payload that will hold the discriminator value. - """ - - mapping: Optional[Dict[str, str]] = None - """ - An object to hold mappings between payload values and schema names or references. - """ - - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ "examples": [ { "propertyName": "petType", @@ -36,4 +26,15 @@ class Config: }, } ] - } + }, + ) + + propertyName: str + """ + **REQUIRED**. The name of the property in the payload that will hold the discriminator value. + """ + + mapping: Optional[Dict[str, str]] = None + """ + An object to hold mappings between payload values and schema names or references. + """ diff --git a/openapi_schemas_pydantic/v3_1_0/encoding.py b/openapi_schemas_pydantic/v3_1_0/encoding.py index f6f33c1..7ae4900 100644 --- a/openapi_schemas_pydantic/v3_1_0/encoding.py +++ b/openapi_schemas_pydantic/v3_1_0/encoding.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Dict, Optional, Union -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict from .reference import Reference @@ -11,6 +11,23 @@ class Encoding(BaseModel): """A single encoding definition applied to a single schema property.""" + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "examples": [ + { + "contentType": "image/png, image/jpeg", + "headers": { + "X-Rate-Limit-Limit": { + "description": "The number of allowed requests in the current period", + "schema": {"type": "integer"}, + } + }, + } + ] + }, + ) + contentType: Optional[str] = None """ The Content-Type for encoding a specific property. @@ -69,19 +86,3 @@ class Encoding(BaseModel): If a value is explicitly defined, then the value of [contentType](https://spec.openapis.org/oas/v3.1.0#encodingContentType) (implicit or explicit) SHALL be ignored. """ - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [ - { - "contentType": "image/png, image/jpeg", - "headers": { - "X-Rate-Limit-Limit": { - "description": "The number of allowed requests in the current period", - "schema": {"type": "integer"}, - } - }, - } - ] - } diff --git a/openapi_schemas_pydantic/v3_1_0/example.py b/openapi_schemas_pydantic/v3_1_0/example.py index a0c8215..efd20c3 100644 --- a/openapi_schemas_pydantic/v3_1_0/example.py +++ b/openapi_schemas_pydantic/v3_1_0/example.py @@ -1,10 +1,25 @@ from typing import Any, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict class Example(BaseModel): - + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "examples": [ + {"summary": "A foo example", "value": {"foo": "bar"}}, + { + "summary": "This is an example in XML", + "externalValue": "http://example.org/examples/address-example.xml", + }, + { + "summary": "This is a text example", + "externalValue": "http://foo.bar/examples/address-example.txt", + }, + ] + }, + ) summary: Optional[str] = None """ Short description for the example. @@ -32,19 +47,3 @@ class Example(BaseModel): The `value` field and `externalValue` field are mutually exclusive. See the rules for resolving [Relative References](https://spec.openapis.org/oas/v3.1.0#relativeReferencesURI). """ - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [ - {"summary": "A foo example", "value": {"foo": "bar"}}, - { - "summary": "This is an example in XML", - "externalValue": "http://example.org/examples/address-example.xml", - }, - { - "summary": "This is a text example", - "externalValue": "http://foo.bar/examples/address-example.txt", - }, - ] - } diff --git a/openapi_schemas_pydantic/v3_1_0/external_documentation.py b/openapi_schemas_pydantic/v3_1_0/external_documentation.py index d3b7c54..3321350 100644 --- a/openapi_schemas_pydantic/v3_1_0/external_documentation.py +++ b/openapi_schemas_pydantic/v3_1_0/external_documentation.py @@ -1,11 +1,18 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict, Extra class ExternalDocumentation(BaseModel): """Allows referencing an external resource for extended documentation.""" + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ + "examples": [{"description": "Find more info here", "url": "https://example.com"}] + }, + ) + description: Optional[str] = None """ A short description of the target documentation. @@ -17,9 +24,3 @@ class ExternalDocumentation(BaseModel): **REQUIRED**. The URL for the target documentation. Value MUST be in the form of a URL. """ - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [{"description": "Find more info here", "url": "https://example.com"}] - } diff --git a/openapi_schemas_pydantic/v3_1_0/header.py b/openapi_schemas_pydantic/v3_1_0/header.py index 2997fba..829a955 100644 --- a/openapi_schemas_pydantic/v3_1_0/header.py +++ b/openapi_schemas_pydantic/v3_1_0/header.py @@ -1,4 +1,4 @@ -from pydantic import Extra, Field +from pydantic import ConfigDict, Extra, Field from typing_extensions import Literal from .parameter import Parameter @@ -15,17 +15,17 @@ class Header(Parameter): (for example, [style](https://spec.openapis.org/oas/v3.1.0#parameterStyle)). """ - name: Literal[""] = Field(default="", const=True) - param_in: Literal["header"] = Field(default="header", const=True, alias="in") - - class Config: - extra = Extra.ignore - allow_population_by_field_name = True - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + populate_by_name=True, + json_schema_extra={ "examples": [ { "description": "The number of allowed requests in the current period", "schema": {"type": "integer"}, } ] - } + }, + ) + name: Literal[""] = Field(default="") + param_in: Literal["header"] = Field(default="header", alias="in") diff --git a/openapi_schemas_pydantic/v3_1_0/info.py b/openapi_schemas_pydantic/v3_1_0/info.py index 40aea6d..b64c56c 100644 --- a/openapi_schemas_pydantic/v3_1_0/info.py +++ b/openapi_schemas_pydantic/v3_1_0/info.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict, Extra from .contact import Contact from .license import License @@ -14,6 +14,30 @@ class Info(BaseModel): convenience. """ + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ + "examples": [ + { + "title": "Sample Pet Store App", + "summary": "A pet store manager.", + "description": "This is a sample server for a pet store.", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "API Support", + "url": "http://www.example.com/support", + "email": "support@example.com", + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, + "version": "1.0.1", + } + ] + }, + ) + title: str """ **REQUIRED**. The title of the API. @@ -51,26 +75,3 @@ class Info(BaseModel): **REQUIRED**. The version of the OpenAPI document (which is distinct from the [OpenAPI Specification version](https://spec.openapis.org/oas/v3.1.0#oasVersion)) or the API implementation version). """ - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [ - { - "title": "Sample Pet Store App", - "summary": "A pet store manager.", - "description": "This is a sample server for a pet store.", - "termsOfService": "http://example.com/terms/", - "contact": { - "name": "API Support", - "url": "http://www.example.com/support", - "email": "support@example.com", - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - }, - "version": "1.0.1", - } - ] - } diff --git a/openapi_schemas_pydantic/v3_1_0/license.py b/openapi_schemas_pydantic/v3_1_0/license.py index 6ff6f88..f469b5b 100644 --- a/openapi_schemas_pydantic/v3_1_0/license.py +++ b/openapi_schemas_pydantic/v3_1_0/license.py @@ -1,11 +1,21 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict, Extra class License(BaseModel): """License information for the exposed API.""" + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ + "examples": [ + {"name": "Apache 2.0", "identifier": "Apache-2.0"}, + {"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}, + ] + }, + ) + name: str """ **REQUIRED**. The license name used for the API. @@ -23,12 +33,3 @@ class License(BaseModel): This MUST be in the form of a URL. The `url` field is mutually exclusive of the `identifier` field. """ - - class Config: - extra = Extra.ignore - schema_extra = { - "examples": [ - {"name": "Apache 2.0", "identifier": "Apache-2.0"}, - {"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}, - ] - } diff --git a/openapi_schemas_pydantic/v3_1_0/link.py b/openapi_schemas_pydantic/v3_1_0/link.py index 1e7695a..5e0ac34 100644 --- a/openapi_schemas_pydantic/v3_1_0/link.py +++ b/openapi_schemas_pydantic/v3_1_0/link.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .server import Server @@ -62,9 +62,9 @@ class Link(BaseModel): A server object to be used by the target operation. """ - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ "examples": [ { "operationId": "getUserAddressByUUID", @@ -75,4 +75,5 @@ class Config: "parameters": {"username": "$response.body#/username"}, }, ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/media_type.py b/openapi_schemas_pydantic/v3_1_0/media_type.py index 94ed927..2aebcb7 100644 --- a/openapi_schemas_pydantic/v3_1_0/media_type.py +++ b/openapi_schemas_pydantic/v3_1_0/media_type.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, ConfigDict, Extra, Field from .encoding import Encoding from .example import Example @@ -49,10 +49,10 @@ class MediaType(BaseModel): when the media type is `multipart` or `application/x-www-form-urlencoded`. """ - class Config: - extra = Extra.ignore - allow_population_by_field_name = True - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + populate_by_name=True, + json_schema_extra={ "examples": [ { "schema": {"$ref": "#/components/schemas/Pet"}, @@ -81,4 +81,5 @@ class Config: }, } ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/oauth_flow.py b/openapi_schemas_pydantic/v3_1_0/oauth_flow.py index 7d2f2c2..d601d7a 100644 --- a/openapi_schemas_pydantic/v3_1_0/oauth_flow.py +++ b/openapi_schemas_pydantic/v3_1_0/oauth_flow.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import AnyUrl, BaseModel, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict, Extra class OAuthFlow(BaseModel): @@ -36,9 +36,9 @@ class OAuthFlow(BaseModel): The map MAY be empty. """ - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ "examples": [ { "authorizationUrl": "https://example.com/api/oauth/dialog", @@ -65,4 +65,5 @@ class Config: }, }, ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/oauth_flows.py b/openapi_schemas_pydantic/v3_1_0/oauth_flows.py index 64e9859..d10218b 100644 --- a/openapi_schemas_pydantic/v3_1_0/oauth_flows.py +++ b/openapi_schemas_pydantic/v3_1_0/oauth_flows.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .oauth_flow import OAuthFlow @@ -32,5 +32,4 @@ class OAuthFlows(BaseModel): Previously called `accessCode` in OpenAPI 2.0. """ - class Config: - extra = Extra.ignore + model_config = ConfigDict(extra=Extra.ignore) diff --git a/openapi_schemas_pydantic/v3_1_0/open_api.py b/openapi_schemas_pydantic/v3_1_0/open_api.py index 237cdbd..9b8bc31 100644 --- a/openapi_schemas_pydantic/v3_1_0/open_api.py +++ b/openapi_schemas_pydantic/v3_1_0/open_api.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .components import Components from .external_documentation import ExternalDocumentation @@ -87,5 +87,4 @@ class OpenAPI(BaseModel): Additional external documentation. """ - class Config: - extra = Extra.ignore + model_config = ConfigDict(extra=Extra.ignore) diff --git a/openapi_schemas_pydantic/v3_1_0/operation.py b/openapi_schemas_pydantic/v3_1_0/operation.py index fa0c999..c6291bf 100644 --- a/openapi_schemas_pydantic/v3_1_0/operation.py +++ b/openapi_schemas_pydantic/v3_1_0/operation.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .callback import Callback from .external_documentation import ExternalDocumentation @@ -105,10 +105,9 @@ class Operation(BaseModel): If an alternative `server` object is specified at the Path Item Object or Root level, it will be overridden by this value. """ - - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ "examples": [ { "tags": ["pet"], @@ -156,4 +155,5 @@ class Config: "security": [{"petstore_auth": ["write:pets", "read:pets"]}], } ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/parameter.py b/openapi_schemas_pydantic/v3_1_0/parameter.py index cfcc24b..1d1d71f 100644 --- a/openapi_schemas_pydantic/v3_1_0/parameter.py +++ b/openapi_schemas_pydantic/v3_1_0/parameter.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, ConfigDict, Extra, Field from .example import Example from .media_type import MediaType @@ -132,11 +132,10 @@ class Parameter(BaseModel): The key is the media type and the value describes it. The map MUST only contain one entry. """ - - class Config: - extra = Extra.ignore - allow_population_by_field_name = True - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + populate_by_name=True, + json_schema_extra={ "examples": [ { "name": "token", @@ -185,4 +184,5 @@ class Config: }, }, ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/path_item.py b/openapi_schemas_pydantic/v3_1_0/path_item.py index cabd06b..1b3a6a7 100644 --- a/openapi_schemas_pydantic/v3_1_0/path_item.py +++ b/openapi_schemas_pydantic/v3_1_0/path_item.py @@ -1,6 +1,6 @@ from typing import List, Optional, Union -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, ConfigDict, Extra, Field from .operation import Operation from .parameter import Parameter @@ -91,11 +91,10 @@ class PathItem(BaseModel): The list can use the [Reference Object](https://spec.openapis.org/oas/v3.1.0#referenceObject) to link to parameters that are defined at the [OpenAPI Object's components/parameters](https://spec.openapis.org/oas/v3.1.0#componentsParameters). """ - - class Config: - extra = Extra.ignore - allow_population_by_field_name = True - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + populate_by_name=True, + json_schema_extra={ "examples": [ { "get": { @@ -136,4 +135,5 @@ class Config: ], } ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/reference.py b/openapi_schemas_pydantic/v3_1_0/reference.py index 812b1e8..45fd419 100644 --- a/openapi_schemas_pydantic/v3_1_0/reference.py +++ b/openapi_schemas_pydantic/v3_1_0/reference.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, ConfigDict, Extra, Field class Reference(BaseModel): @@ -28,14 +28,14 @@ class Reference(BaseModel): [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. If the referenced object-type does not allow a `description` field, then this field has no effect. """ - - class Config: - extra = Extra.ignore - allow_population_by_field_name = True - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + populate_by_name=True, + json_schema_extra={ "examples": [ {"$ref": "#/components/schemas/Pet"}, {"$ref": "Pet.json"}, {"$ref": "definitions.json#/Pet"}, ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/request_body.py b/openapi_schemas_pydantic/v3_1_0/request_body.py index f574fbb..d960c0e 100644 --- a/openapi_schemas_pydantic/v3_1_0/request_body.py +++ b/openapi_schemas_pydantic/v3_1_0/request_body.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .media_type import MediaType @@ -29,10 +29,9 @@ class RequestBody(BaseModel): """ Determines if the request body is required in the request. Defaults to `false`. """ - - class Config: - extra = Extra.ignore - schema_extra = { + model_config = ConfigDict( + extra=Extra.ignore, + json_schema_extra={ "examples": [ { "description": "user to add to the system", @@ -80,4 +79,5 @@ class Config: }, }, ] - } + }, + ) diff --git a/openapi_schemas_pydantic/v3_1_0/response.py b/openapi_schemas_pydantic/v3_1_0/response.py index f4082b9..ad6ac1e 100644 --- a/openapi_schemas_pydantic/v3_1_0/response.py +++ b/openapi_schemas_pydantic/v3_1_0/response.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict, Extra from .header import Header from .link import Link @@ -40,10 +40,9 @@ class Response(BaseModel): The key of the map is a short name for the link, following the naming constraints of the names for `Component Objects 1.10.0,<2.0.0", - "email-validator >=1.2.1,<2.0.0" -] +dependencies = ["pydantic>=2.0.0,<3.0.0", "email-validator >=1.2.1,<2.0.0"] [project.urls] Homepage = "https://github.com/dymmond/openapi-schemas-pydantic" @@ -51,7 +52,11 @@ test = [ ] dev = [ - "pre-commit >=2.17.0,<3.0.0", + "black>=23.0.0,<30.0.0", + "isort>=5.12.0,<6.0.0", + "mypy==1.4.1", + "pre-commit>=2.17.0,<3.0.0", + "ruff>=0.0.275,<1.0.0", ] doc = [ @@ -64,6 +69,23 @@ doc = [ "pyyaml >=5.3.1,<7.0.0", ] +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "C", # flake8-comprehensions + "B", # flake8-bugbear +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex +] + +exclude = ["docs/*"] + + [tool.hatch.version] path = "openapi_schemas_pydantic/__init__.py" @@ -73,6 +95,17 @@ known_third_party = ["esmerald", "pydantic", "starlette"] [tool.mypy] strict = true +warn_unused_configs = true +warn_unreachable = true +warn_return_any = true +disallow_untyped_decorators = true +disallow_any_generics = false +implicit_reexport = false +show_error_codes = true +disallow_incomplete_defs = true +disable_error_code = "attr-defined" +warn_unused_ignores = true +warn_redundant_casts = true [[tool.mypy.overrides]] module = "openapi-schemas-pydantic.tests.*" @@ -80,14 +113,9 @@ ignore_missing_imports = true check_untyped_defs = true [tool.pytest.ini_options] -addopts = [ - "--strict-config", - "--strict-markers", -] +addopts = ["--strict-config", "--strict-markers"] xfail_strict = true junit_family = "xunit2" [tool.hatch.build.targets.sdist] -include = [ - "/openapi_schemas_pydantic", -] +include = ["/openapi_schemas_pydantic"] diff --git a/scripts/clean b/scripts/clean index 2f74c94..9f0d062 100755 --- a/scripts/clean +++ b/scripts/clean @@ -9,8 +9,8 @@ fi if [ -d 'htmlcov' ] ; then rm -r htmlcov fi -if [ -d 'openapi-schemas-pydantic.egg-info' ] ; then - rm -r esmerald.egg-info +if [ -d 'openapi_schemas_pydantic.egg-info' ] ; then + rm -r openapi_schemas_pydantic.egg-info fi if [ -d '.hypothesis' ] ; then rm -r .hypothesis @@ -20,4 +20,7 @@ if [ -d '.mypy_cache' ] ; then fi if [ -d '.pytest_cache' ] ; then rm -r .pytest_cache -fi \ No newline at end of file +fi +if [ -d '.ruff_cache' ] ; then + rm -r .ruff_cache +fi diff --git a/scripts/format b/scripts/format deleted file mode 100755 index 9846600..0000000 --- a/scripts/format +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -e - -export PREFIX="" -if [ "$VIRTUAL_ENV" != '' ]; then - export PREFIX="$VIRTUAL_ENV/bin/" -elif [ -d 'venv' ] ; then - export PREFIX="venv/bin/" -fi -export SOURCE_FILES="openapi_schemas_pydantic tests" -export EXCLUDE=__init__.py - -set -x - -${PREFIX}autoflake --remove-all-unused-imports --remove-unused-variables --exclude $EXCLUDE --in-place --recursive $SOURCE_FILES -${PREFIX}black $SOURCE_FILES --line-length 99 -${PREFIX}isort $SOURCE_FILE \ No newline at end of file diff --git a/scripts/lint b/scripts/lint index 22366f6..345fb9a 100755 --- a/scripts/lint +++ b/scripts/lint @@ -1,7 +1,4 @@ -#!/usr/bin/env bash - -set -e -set -x +#!/bin/sh -e export PREFIX="" if [ "$VIRTUAL_ENV" != '' ]; then @@ -9,12 +6,12 @@ if [ "$VIRTUAL_ENV" != '' ]; then elif [ -d 'venv' ] ; then export PREFIX="venv/bin/" fi -export SOURCE_FILES="openapi_schemas_pydantic tests" +export SOURCE_FILES="openapi_schemas_pydantic tests docs" export EXCLUDE=__init__.py set -x -# mypy esmerald -${PREFIX}autoflake --remove-all-unused-imports --remove-unused-variables --exclude $EXCLUDE --in-place --recursive $SOURCE_FILES -${PREFIX}black $SOURCE_FILES --check --line-length 99 -${PREFIX}isort $SOURCE_FILES --check-only +${PREFIX}ruff $SOURCE_FILES --fix --line-length 99 +${PREFIX}black $SOURCE_FILES --line-length 99 +${PREFIX}isort $SOURCE_FILES --project=openapi_schemas_pydantic --line-length 99 +# ${PREFIX}mypy openapi_schemas_pydantic diff --git a/scripts/test b/scripts/test index da1440b..3731832 100755 --- a/scripts/test +++ b/scripts/test @@ -8,7 +8,7 @@ fi set -ex if [ -z $GITHUB_ACTIONS ]; then - scripts/format + scripts/lint fi -${PREFIX}pytest $@ \ No newline at end of file +${PREFIX}pytest $@ diff --git a/tests/v3_1_0/test_alias.py b/tests/v3_1_0/test_alias.py index bb42677..e70fcd1 100644 --- a/tests/v3_1_0/test_alias.py +++ b/tests/v3_1_0/test_alias.py @@ -11,52 +11,52 @@ def test_header_alias() -> None: header_1 = Header(param_in="header") - header_2 = Header.parse_obj({"param_in": "header"}) - header_3 = Header.parse_obj({"in": "header"}) + header_2 = Header.model_validate({"param_in": "header"}) + header_3 = Header.model_validate({"in": "header"}) assert header_1 == header_2 == header_3 def test_media_type_alias() -> None: media_type_1 = MediaType(media_type_schema=Schema()) - media_type_2 = MediaType.parse_obj({"media_type_schema": Schema()}) - media_type_3 = MediaType.parse_obj({"schema": Schema()}) + media_type_2 = MediaType.model_validate({"media_type_schema": Schema()}) + media_type_3 = MediaType.model_validate({"schema": Schema()}) assert media_type_1 == media_type_2 == media_type_3 def test_parameter_alias() -> None: parameter_1 = Parameter(name="test", param_in="path", param_schema=Schema()) - parameter_2 = Parameter.parse_obj( + parameter_2 = Parameter.model_validate( {"name": "test", "param_in": "path", "param_schema": Schema()} ) - parameter_3 = Parameter.parse_obj({"name": "test", "in": "path", "schema": Schema()}) + parameter_3 = Parameter.model_validate({"name": "test", "in": "path", "schema": Schema()}) assert parameter_1 == parameter_2 == parameter_3 def test_path_item_alias() -> None: path_item_1 = PathItem(ref="#/dummy") - path_item_2 = PathItem.parse_obj({"ref": "#/dummy"}) - path_item_3 = PathItem.parse_obj({"$ref": "#/dummy"}) + path_item_2 = PathItem.model_validate({"ref": "#/dummy"}) + path_item_3 = PathItem.model_validate({"$ref": "#/dummy"}) assert path_item_1 == path_item_2 == path_item_3 def test_reference_alias() -> None: reference_1 = Reference(ref="#/dummy") - reference_2 = Reference.parse_obj({"ref": "#/dummy"}) - reference_3 = Reference.parse_obj({"$ref": "#/dummy"}) + reference_2 = Reference.model_validate({"ref": "#/dummy"}) + reference_3 = Reference.model_validate({"$ref": "#/dummy"}) assert reference_1 == reference_2 == reference_3 def test_security_scheme() -> None: security_scheme_1 = SecurityScheme(type="apiKey", security_scheme_in="header") - security_scheme_2 = SecurityScheme.parse_obj( + security_scheme_2 = SecurityScheme.model_validate( {"type": "apiKey", "security_scheme_in": "header"} ) - security_scheme_3 = SecurityScheme.parse_obj({"type": "apiKey", "in": "header"}) + security_scheme_3 = SecurityScheme.model_validate({"type": "apiKey", "in": "header"}) assert security_scheme_1 == security_scheme_2 == security_scheme_3 def test_schema() -> None: schema_1 = Schema(schema_not=Schema(), schema_format="email") - schema_2 = Schema.parse_obj({"schema_not": Schema(), "schema_format": "email"}) - schema_3 = Schema.parse_obj({"not": Schema(), "format": "email"}) + schema_2 = Schema.model_validate({"schema_not": Schema(), "schema_format": "email"}) + schema_3 = Schema.model_validate({"not": Schema(), "format": "email"}) assert schema_1 == schema_2 == schema_3 diff --git a/tests/v3_1_0/test_config_example.py b/tests/v3_1_0/test_config_example.py index 7d001e6..b845cec 100644 --- a/tests/v3_1_0/test_config_example.py +++ b/tests/v3_1_0/test_config_example.py @@ -66,8 +66,8 @@ def test_config_example() -> None: SecurityRequirement, ] for schema_type in all_types: - if getattr(schema_type, "Config", None) and getattr(schema_type.Config, "schema_extra", None): # type: ignore - examples = schema_type.Config.schema_extra.get("examples") # type: ignore + if getattr(schema_type, "Config", None) and getattr(schema_type.Config, "json_schema_extra", None): # type: ignore + examples = schema_type.Config.json_schema_extra.get("examples") # type: ignore for example_dict in examples: obj = schema_type(**example_dict) assert obj.__fields_set__ diff --git a/tests/v3_1_0/test_empty_schema.py b/tests/v3_1_0/test_empty_schema.py index c29cfde..39547eb 100644 --- a/tests/v3_1_0/test_empty_schema.py +++ b/tests/v3_1_0/test_empty_schema.py @@ -2,5 +2,5 @@ def test_empty_schema() -> None: - schema = Schema.parse_obj({}) + schema = Schema.model_validate({}) assert schema == Schema() diff --git a/tests/v3_1_0/test_example.py b/tests/v3_1_0/test_example.py index 836c2d9..ae285f0 100644 --- a/tests/v3_1_0/test_example.py +++ b/tests/v3_1_0/test_example.py @@ -4,7 +4,7 @@ def test_readme_example() -> None: open_api_1 = readme_example_1() assert open_api_1 - open_api_json_1 = open_api_1.json(by_alias=True, exclude_none=True, indent=2) + open_api_json_1 = open_api_1.model_dump_json(by_alias=True, exclude_none=True, indent=2) assert open_api_json_1 open_api_2 = readme_example_2() @@ -27,7 +27,7 @@ def readme_example_1() -> OpenAPI: def readme_example_2() -> OpenAPI: """Construct OpenAPI from raw data object.""" - return OpenAPI.parse_obj( + return OpenAPI.model_validate( { "info": {"title": "My own API", "version": "v0.0.1"}, "paths": {"/ping": {"get": {"responses": {"200": {"description": "pong"}}}}}, @@ -37,7 +37,7 @@ def readme_example_2() -> OpenAPI: def readme_example_3() -> OpenAPI: """Construct OpenAPI from mixed object.""" - return OpenAPI.parse_obj( + return OpenAPI.model_validate( { "info": {"title": "My own API", "version": "v0.0.1"}, "paths": {"/ping": PathItem(get={"responses": {"200": Response(description="pong")}})}, # type: ignore diff --git a/tests/v3_1_0/test_schema.py b/tests/v3_1_0/test_schema.py index 8f14874..46ffd09 100644 --- a/tests/v3_1_0/test_schema.py +++ b/tests/v3_1_0/test_schema.py @@ -2,7 +2,7 @@ def test_schema_parse_obj() -> None: - parsed_schema = Schema.parse_obj( + parsed_schema = Schema.model_validate( { "title": "reference list", "description": "schema for list of reference type", diff --git a/tests/v3_1_0/test_security_scheme.py b/tests/v3_1_0/test_security_scheme.py index 5c46f83..148ddb3 100644 --- a/tests/v3_1_0/test_security_scheme.py +++ b/tests/v3_1_0/test_security_scheme.py @@ -10,18 +10,18 @@ def test_security_scheme_issue_5() -> None: type="openIdConnect", openIdConnectUrl="https://example.com/openIdConnect" ) assert isinstance(security_scheme_1.openIdConnectUrl, (AnyUrl, str)) - assert security_scheme_1.json(by_alias=True, exclude_none=True) == ( + assert security_scheme_1.model_dump_json(by_alias=True, exclude_none=True) == ( '{"type": "openIdConnect", "openIdConnectUrl": "https://example.com/openIdConnect"}' ) security_scheme_2 = SecurityScheme(type="openIdConnect", openIdConnectUrl="/openIdConnect") assert isinstance(security_scheme_2.openIdConnectUrl, str) - assert security_scheme_2.json(by_alias=True, exclude_none=True) == ( + assert security_scheme_2.model_dump_json(by_alias=True, exclude_none=True) == ( '{"type": "openIdConnect", "openIdConnectUrl": "/openIdConnect"}' ) security_scheme_3 = SecurityScheme(type="openIdConnect", openIdConnectUrl="openIdConnect") assert isinstance(security_scheme_3.openIdConnectUrl, str) - assert security_scheme_3.json(by_alias=True, exclude_none=True) == ( + assert security_scheme_3.model_dump_json(by_alias=True, exclude_none=True) == ( '{"type": "openIdConnect", "openIdConnectUrl": "openIdConnect"}' ) diff --git a/tests/v3_1_0/test_swagger_openapi_v3.py b/tests/v3_1_0/test_swagger_openapi_v3.py index 787b3ee..bfdbcab 100644 --- a/tests/v3_1_0/test_swagger_openapi_v3.py +++ b/tests/v3_1_0/test_swagger_openapi_v3.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import Field +from pydantic import ConfigDict, Field from openapi_schemas_pydantic.v3_1_0 import OpenAPI, Operation, PathItem @@ -17,8 +17,7 @@ class ExtendedOperation(Operation): default=None, alias="x-codegen-request-body-name" ) - class Config: - allow_population_by_field_name = True + model_config = ConfigDict(populate_by_name=True) class ExtendedPathItem(PathItem): diff --git a/tests/v3_1_0/test_util.py b/tests/v3_1_0/test_util.py index 0769fd1..ac716de 100644 --- a/tests/v3_1_0/test_util.py +++ b/tests/v3_1_0/test_util.py @@ -39,7 +39,7 @@ class PongResponse(BaseModel): def test_construct_open_api_parse_obj() -> None: - open_api = OpenAPI.parse_obj( + open_api = OpenAPI.model_validate( { "info": {"title": "My own API", "version": "v0.0.1"}, "paths": { From 2c45668459ca45eba47ba7551392deff69ff5fed Mon Sep 17 00:00:00 2001 From: tarsil Date: Mon, 3 Jul 2023 18:13:38 +0100 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=9D=20Update=20model=5Fjson=5Fsche?= =?UTF-8?q?ma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi_schemas_pydantic/utils/utils.py | 17 ++++++++++------- openapi_schemas_pydantic/v3_1_0/__init__.py | 3 +++ tests/v3_1_0/test_util.py | 11 ++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/openapi_schemas_pydantic/utils/utils.py b/openapi_schemas_pydantic/utils/utils.py index bdd38b8..4564a30 100644 --- a/openapi_schemas_pydantic/utils/utils.py +++ b/openapi_schemas_pydantic/utils/utils.py @@ -10,6 +10,7 @@ REF_PREFIX = "#/components/schemas/" SCHEMA_NAME_ATTRIBUTE = "__schema_name__" +MODE = "validation" T = TypeVar("T", bound=v3_1_0.OpenAPI) @@ -34,7 +35,7 @@ def construct_open_api_with_schema_class( new OpenAPI object with "#/components/schemas" values updated. If there is no update in "#/components/schemas" values, the original `open_api` will be returned. """ - copied_schema = open_api_schema.copy(deep=True) + copied_schema = open_api_schema.model_copy(deep=True) schema_classes = list( extract_pydantic_types_to_openapi_components(obj=copied_schema, ref_class=v3_1_0.Reference) ) @@ -48,17 +49,19 @@ def construct_open_api_with_schema_class( copied_schema.components.schemas = cast("Dict[str, Any]", {}) schema_classes = [ - cls - if not hasattr(cls, "__schema_name__") - else create_model(getattr(cls, SCHEMA_NAME_ATTRIBUTE), __base__=cls) + (cls, MODE) + if not hasattr(cls, SCHEMA_NAME_ATTRIBUTE) + else (create_model(getattr(cls, SCHEMA_NAME_ATTRIBUTE), __base__=cls), MODE) for cls in schema_classes ] - schema_classes.sort(key=lambda x: x.__name__) - schema_definitions = models_json_schema(schema_classes, ref_template=REF_PREFIX)["definitions"] + + schema_classes.sort(key=lambda x: x[0].__name__) + _, json_schema = models_json_schema(schema_classes) + copied_schema.components.schemas.update( { key: v3_1_0.Schema.model_validate(schema_dict) - for key, schema_dict in schema_definitions.items() + for key, schema_dict in json_schema["$defs"].items() } ) return copied_schema diff --git a/openapi_schemas_pydantic/v3_1_0/__init__.py b/openapi_schemas_pydantic/v3_1_0/__init__.py index bbfcb39..f09b484 100644 --- a/openapi_schemas_pydantic/v3_1_0/__init__.py +++ b/openapi_schemas_pydantic/v3_1_0/__init__.py @@ -39,6 +39,9 @@ # resolve forward references Encoding.model_rebuild() Schema.model_rebuild() +Header.model_rebuild() +Reference.model_rebuild() +Operation.model_rebuild() __all__ = [ "Callback", diff --git a/tests/v3_1_0/test_util.py b/tests/v3_1_0/test_util.py index ac716de..fd43147 100644 --- a/tests/v3_1_0/test_util.py +++ b/tests/v3_1_0/test_util.py @@ -69,8 +69,9 @@ def test_construct_open_api_parse_obj() -> None: }, } ) + result = construct_open_api_with_schema_class(open_api) - assert result.dict(exclude_none=True) == { + assert result.model_dump(exclude_none=True) == { "openapi": "3.1.0", "info": {"title": "My own API", "version": "v0.0.1"}, "servers": [{"url": "/"}], @@ -180,7 +181,7 @@ def test_construct_open_api_with_schema_class() -> None: ) result = construct_open_api_with_schema_class(open_api) - assert result.dict(exclude_none=True) == { + assert result.model_dump(exclude_none=True) == { "openapi": "3.1.0", "info": {"title": "My own API", "version": "v0.0.1"}, "servers": [{"url": "/"}], @@ -314,7 +315,7 @@ def test_handling_of_models_with_same_name() -> None: }, ) result = construct_open_api_with_schema_class(open_api) - assert result.dict(exclude_none=True) == { + assert result.model_dump(exclude_none=True) == { "openapi": "3.1.0", "info": {"title": "My own API", "version": "v0.0.1"}, "servers": [{"url": "/"}], @@ -426,7 +427,7 @@ def test_handling_of_models_with_same_name() -> None: "type": "object", "required": ["req_foo", "req_bar"], "title": "RenamedPingRequest", - "description": "Ping Request.", + # "description": "Ping Request.", }, "RenamedPingResponse": { "properties": { @@ -444,7 +445,7 @@ def test_handling_of_models_with_same_name() -> None: "type": "object", "required": ["resp_foo", "resp_bar"], "title": "RenamedPingResponse", - "description": "Ping response.", + # "description": "Ping response.", }, } }, From 229ffe8e7920f80b3449600dae2552ef183cf828 Mon Sep 17 00:00:00 2001 From: tarsil Date: Mon, 3 Jul 2023 18:25:51 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=AA=9B=20Another=20use=20case=20for?= =?UTF-8?q?=20the=20$defs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi_schemas_pydantic/v3_1_0/schema.py | 4 ++-- tests/v3_1_0/test_pydantic_field.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openapi_schemas_pydantic/v3_1_0/schema.py b/openapi_schemas_pydantic/v3_1_0/schema.py index 2b1b636..a689989 100644 --- a/openapi_schemas_pydantic/v3_1_0/schema.py +++ b/openapi_schemas_pydantic/v3_1_0/schema.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra, Field +from pydantic import BaseModel, ConfigDict, Field from .discriminator import Discriminator from .external_documentation import ExternalDocumentation @@ -824,7 +824,7 @@ class Schema(BaseModel): Use of example is discouraged, and later versions of this specification may remove it. """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/tests/v3_1_0/test_pydantic_field.py b/tests/v3_1_0/test_pydantic_field.py index 80d9324..2033f09 100644 --- a/tests/v3_1_0/test_pydantic_field.py +++ b/tests/v3_1_0/test_pydantic_field.py @@ -28,12 +28,12 @@ def test_pydantic_discriminator_openapi_generation() -> None: "data": Schema( oneOf=[ Reference( - ref="#/components/schemas/DataAModel", + ref="#/$defs/DataAModel", summary=None, description=None, ), Reference( - ref="#/components/schemas/DataBModel", + ref="#/$defs/DataBModel", summary=None, description=None, ), @@ -42,8 +42,8 @@ def test_pydantic_discriminator_openapi_generation() -> None: discriminator=Discriminator( propertyName="kind", mapping={ - "a": "#/components/schemas/DataAModel", - "b": "#/components/schemas/DataBModel", + "a": "#/$defs/DataAModel", + "b": "#/$defs/DataBModel", }, ), ) From 0be437090d8ceb5c55e42cfb30e6011bf81a534c Mon Sep 17 00:00:00 2001 From: tarsil Date: Tue, 4 Jul 2023 10:17:16 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=E2=A4=B4=EF=B8=8F=20Upgrade=20to=20pydanti?= =?UTF-8?q?c=202.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi_schemas_pydantic/utils/utils.py | 2 +- .../v3_1_0/external_documentation.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/header.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/info.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/license.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/link.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/media_type.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/oauth_flow.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/oauth_flows.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/open_api.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/operation.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/parameter.py | 6 +++--- openapi_schemas_pydantic/v3_1_0/path_item.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/reference.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/request_body.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/response.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/security_scheme.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/server.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/server_variable.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/tag.py | 4 ++-- openapi_schemas_pydantic/v3_1_0/xml.py | 4 ++-- tests/v3_1_0/test_security_scheme.py | 8 +++----- tests/v3_1_0/test_swagger_openapi_v3.py | 11 ++++++++++- 23 files changed, 55 insertions(+), 48 deletions(-) diff --git a/openapi_schemas_pydantic/utils/utils.py b/openapi_schemas_pydantic/utils/utils.py index 4564a30..88e788d 100644 --- a/openapi_schemas_pydantic/utils/utils.py +++ b/openapi_schemas_pydantic/utils/utils.py @@ -83,7 +83,7 @@ def extract_pydantic_types_to_openapi_components( """ pydantic_schemas: Set[Type[BaseModel]] = set() if isinstance(obj, BaseModel): - fields = obj.__fields_set__ + fields = obj.model_fields_set for field in fields: child_obj = getattr(obj, field) if isinstance(child_obj, OpenAPI310PydanticSchema): diff --git a/openapi_schemas_pydantic/v3_1_0/external_documentation.py b/openapi_schemas_pydantic/v3_1_0/external_documentation.py index 3321350..03b71a5 100644 --- a/openapi_schemas_pydantic/v3_1_0/external_documentation.py +++ b/openapi_schemas_pydantic/v3_1_0/external_documentation.py @@ -1,13 +1,13 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, ConfigDict, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict class ExternalDocumentation(BaseModel): """Allows referencing an external resource for extended documentation.""" model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [{"description": "Find more info here", "url": "https://example.com"}] }, diff --git a/openapi_schemas_pydantic/v3_1_0/header.py b/openapi_schemas_pydantic/v3_1_0/header.py index 829a955..7f65f6b 100644 --- a/openapi_schemas_pydantic/v3_1_0/header.py +++ b/openapi_schemas_pydantic/v3_1_0/header.py @@ -1,4 +1,4 @@ -from pydantic import ConfigDict, Extra, Field +from pydantic import ConfigDict, Field from typing_extensions import Literal from .parameter import Parameter @@ -16,7 +16,7 @@ class Header(Parameter): """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/openapi_schemas_pydantic/v3_1_0/info.py b/openapi_schemas_pydantic/v3_1_0/info.py index b64c56c..83ccca4 100644 --- a/openapi_schemas_pydantic/v3_1_0/info.py +++ b/openapi_schemas_pydantic/v3_1_0/info.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, ConfigDict, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict from .contact import Contact from .license import License @@ -15,7 +15,7 @@ class Info(BaseModel): """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ { diff --git a/openapi_schemas_pydantic/v3_1_0/license.py b/openapi_schemas_pydantic/v3_1_0/license.py index f469b5b..3a9b4a0 100644 --- a/openapi_schemas_pydantic/v3_1_0/license.py +++ b/openapi_schemas_pydantic/v3_1_0/license.py @@ -1,13 +1,13 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, ConfigDict, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict class License(BaseModel): """License information for the exposed API.""" model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ {"name": "Apache 2.0", "identifier": "Apache-2.0"}, diff --git a/openapi_schemas_pydantic/v3_1_0/link.py b/openapi_schemas_pydantic/v3_1_0/link.py index 5e0ac34..797d3c0 100644 --- a/openapi_schemas_pydantic/v3_1_0/link.py +++ b/openapi_schemas_pydantic/v3_1_0/link.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .server import Server @@ -63,7 +63,7 @@ class Link(BaseModel): """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ { diff --git a/openapi_schemas_pydantic/v3_1_0/media_type.py b/openapi_schemas_pydantic/v3_1_0/media_type.py index 2aebcb7..36704c2 100644 --- a/openapi_schemas_pydantic/v3_1_0/media_type.py +++ b/openapi_schemas_pydantic/v3_1_0/media_type.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra, Field +from pydantic import BaseModel, ConfigDict, Field from .encoding import Encoding from .example import Example @@ -50,7 +50,7 @@ class MediaType(BaseModel): """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/openapi_schemas_pydantic/v3_1_0/oauth_flow.py b/openapi_schemas_pydantic/v3_1_0/oauth_flow.py index d601d7a..dce7592 100644 --- a/openapi_schemas_pydantic/v3_1_0/oauth_flow.py +++ b/openapi_schemas_pydantic/v3_1_0/oauth_flow.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import AnyUrl, BaseModel, ConfigDict, Extra +from pydantic import AnyUrl, BaseModel, ConfigDict class OAuthFlow(BaseModel): @@ -37,7 +37,7 @@ class OAuthFlow(BaseModel): """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ { diff --git a/openapi_schemas_pydantic/v3_1_0/oauth_flows.py b/openapi_schemas_pydantic/v3_1_0/oauth_flows.py index d10218b..00ad492 100644 --- a/openapi_schemas_pydantic/v3_1_0/oauth_flows.py +++ b/openapi_schemas_pydantic/v3_1_0/oauth_flows.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .oauth_flow import OAuthFlow @@ -32,4 +32,4 @@ class OAuthFlows(BaseModel): Previously called `accessCode` in OpenAPI 2.0. """ - model_config = ConfigDict(extra=Extra.ignore) + model_config = ConfigDict(extra="ignore") diff --git a/openapi_schemas_pydantic/v3_1_0/open_api.py b/openapi_schemas_pydantic/v3_1_0/open_api.py index 9b8bc31..eaf70eb 100644 --- a/openapi_schemas_pydantic/v3_1_0/open_api.py +++ b/openapi_schemas_pydantic/v3_1_0/open_api.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .components import Components from .external_documentation import ExternalDocumentation @@ -87,4 +87,4 @@ class OpenAPI(BaseModel): Additional external documentation. """ - model_config = ConfigDict(extra=Extra.ignore) + model_config = ConfigDict(extra="ignore") diff --git a/openapi_schemas_pydantic/v3_1_0/operation.py b/openapi_schemas_pydantic/v3_1_0/operation.py index c6291bf..1db6f1a 100644 --- a/openapi_schemas_pydantic/v3_1_0/operation.py +++ b/openapi_schemas_pydantic/v3_1_0/operation.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .callback import Callback from .external_documentation import ExternalDocumentation @@ -106,7 +106,7 @@ class Operation(BaseModel): it will be overridden by this value. """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ { diff --git a/openapi_schemas_pydantic/v3_1_0/parameter.py b/openapi_schemas_pydantic/v3_1_0/parameter.py index 1d1d71f..ed0267f 100644 --- a/openapi_schemas_pydantic/v3_1_0/parameter.py +++ b/openapi_schemas_pydantic/v3_1_0/parameter.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra, Field +from pydantic import BaseModel, ConfigDict, Field from .example import Example from .media_type import MediaType @@ -77,7 +77,7 @@ class Parameter(BaseModel): - for `cookie` - `form`. """ - explode: bool = False + explode: Optional[bool] = None """ When this is true, parameter values of type `array` or `object` generate separate parameters for each value of the array or key-value pair of the map. @@ -133,7 +133,7 @@ class Parameter(BaseModel): The map MUST only contain one entry. """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/openapi_schemas_pydantic/v3_1_0/path_item.py b/openapi_schemas_pydantic/v3_1_0/path_item.py index 1b3a6a7..04b356c 100644 --- a/openapi_schemas_pydantic/v3_1_0/path_item.py +++ b/openapi_schemas_pydantic/v3_1_0/path_item.py @@ -1,6 +1,6 @@ from typing import List, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra, Field +from pydantic import BaseModel, ConfigDict, Field from .operation import Operation from .parameter import Parameter @@ -92,7 +92,7 @@ class PathItem(BaseModel): [OpenAPI Object's components/parameters](https://spec.openapis.org/oas/v3.1.0#componentsParameters). """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/openapi_schemas_pydantic/v3_1_0/reference.py b/openapi_schemas_pydantic/v3_1_0/reference.py index 45fd419..85750cf 100644 --- a/openapi_schemas_pydantic/v3_1_0/reference.py +++ b/openapi_schemas_pydantic/v3_1_0/reference.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, ConfigDict, Extra, Field +from pydantic import BaseModel, ConfigDict, Field class Reference(BaseModel): @@ -29,7 +29,7 @@ class Reference(BaseModel): If the referenced object-type does not allow a `description` field, then this field has no effect. """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", populate_by_name=True, json_schema_extra={ "examples": [ diff --git a/openapi_schemas_pydantic/v3_1_0/request_body.py b/openapi_schemas_pydantic/v3_1_0/request_body.py index d960c0e..3940b18 100644 --- a/openapi_schemas_pydantic/v3_1_0/request_body.py +++ b/openapi_schemas_pydantic/v3_1_0/request_body.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .media_type import MediaType @@ -30,7 +30,7 @@ class RequestBody(BaseModel): Determines if the request body is required in the request. Defaults to `false`. """ model_config = ConfigDict( - extra=Extra.ignore, + extra="ignore", json_schema_extra={ "examples": [ { diff --git a/openapi_schemas_pydantic/v3_1_0/response.py b/openapi_schemas_pydantic/v3_1_0/response.py index ad6ac1e..5c13a3f 100644 --- a/openapi_schemas_pydantic/v3_1_0/response.py +++ b/openapi_schemas_pydantic/v3_1_0/response.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel, ConfigDict, Extra +from pydantic import BaseModel, ConfigDict from .header import Header from .link import Link @@ -41,7 +41,7 @@ class Response(BaseModel): following the naming constraints of the names for `Component Objects None: - """https://github.com/kuimono/openapi-schema-pydantic/issues/5.""" - security_scheme_1 = SecurityScheme( type="openIdConnect", openIdConnectUrl="https://example.com/openIdConnect" ) assert isinstance(security_scheme_1.openIdConnectUrl, (AnyUrl, str)) assert security_scheme_1.model_dump_json(by_alias=True, exclude_none=True) == ( - '{"type": "openIdConnect", "openIdConnectUrl": "https://example.com/openIdConnect"}' + '{"type":"openIdConnect","openIdConnectUrl":"https://example.com/openIdConnect"}' ) security_scheme_2 = SecurityScheme(type="openIdConnect", openIdConnectUrl="/openIdConnect") assert isinstance(security_scheme_2.openIdConnectUrl, str) assert security_scheme_2.model_dump_json(by_alias=True, exclude_none=True) == ( - '{"type": "openIdConnect", "openIdConnectUrl": "/openIdConnect"}' + '{"type":"openIdConnect","openIdConnectUrl":"/openIdConnect"}' ) security_scheme_3 = SecurityScheme(type="openIdConnect", openIdConnectUrl="openIdConnect") assert isinstance(security_scheme_3.openIdConnectUrl, str) assert security_scheme_3.model_dump_json(by_alias=True, exclude_none=True) == ( - '{"type": "openIdConnect", "openIdConnectUrl": "openIdConnect"}' + '{"type":"openIdConnect","openIdConnectUrl":"openIdConnect"}' ) diff --git a/tests/v3_1_0/test_swagger_openapi_v3.py b/tests/v3_1_0/test_swagger_openapi_v3.py index bfdbcab..e113efc 100644 --- a/tests/v3_1_0/test_swagger_openapi_v3.py +++ b/tests/v3_1_0/test_swagger_openapi_v3.py @@ -1,12 +1,18 @@ +import json from typing import Dict, Optional from pydantic import ConfigDict, Field from openapi_schemas_pydantic.v3_1_0 import OpenAPI, Operation, PathItem +from openapi_schemas_pydantic.v3_1_0 import Reference as Reference # noqa def test_swagger_openapi_v3() -> None: - open_api = ExtendedOpenAPI.parse_file("tests/data/swagger_openapi_v3.0.1.json") + location = "tests/data/swagger_openapi_v3.0.1.json" + with open(location) as loc: + data = json.load(loc) + + open_api = ExtendedOpenAPI.model_validate(data) assert open_api @@ -33,3 +39,6 @@ class ExtendedPathItem(PathItem): class ExtendedOpenAPI(OpenAPI): paths: Dict[str, ExtendedPathItem] # type: ignore + + +ExtendedOpenAPI.model_rebuild() From 2b8f90397190f171eb8f43fd2e8b58ddac0c4276 Mon Sep 17 00:00:00 2001 From: tarsil Date: Tue, 4 Jul 2023 10:22:43 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=AA=9B=20Drop=20support=20for=20pytho?= =?UTF-8?q?n=203.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 2 +- .github/workflows/test-suite.yml | 5 +++-- docs/release-notes.md | 9 +++++++-- pyproject.toml | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c965549..c66c68c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-python@v4" with: - python-version: 3.7 + python-version: 3.8 - name: "Install dependencies" run: "scripts/install" - name: Install build dependencies diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 43d1266..3b117e4 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -3,7 +3,8 @@ name: Test Suite on: push: - branches: ["main"] + branches: + - "**" pull_request: branches: ["main"] schedule: @@ -16,7 +17,7 @@ jobs: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: "actions/checkout@v3" diff --git a/docs/release-notes.md b/docs/release-notes.md index 82fcfbe..858330b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,8 +1,13 @@ # Release Notes -## 1.0.0 +## 2.0.0 + +### Changed -October 23, 2022. +- This can cause breaking changes. This is an upgrade to pydantic 2.0 internally. When installing it, +please be aware this will require at least pydantic 2.0 in your system. + +## 1.0.0 ### Added diff --git a/pyproject.toml b/pyproject.toml index f32b292..e1a29ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ['version'] description = "OpenAPI Schema using pydantic. Forked for Esmerald." long_description = "OpenAPI Schema using pydantic. Forked for Esmerald." authors = [{ name = "Tiago Silva", email = "tiago.silva@dymmond.com" }] -requires-python = ">=3.7" +requires-python = ">=3.8" readme = "README.md" license = "MIT" keywords = [ From cb9dbead1a49eb1c07bde4fd4e3e707bd49a6e25 Mon Sep 17 00:00:00 2001 From: tarsil Date: Tue, 4 Jul 2023 10:27:17 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=AA=B2=20pydantic=20extras=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi_schemas_pydantic/utils/constants.py | 2 +- pyproject.toml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openapi_schemas_pydantic/utils/constants.py b/openapi_schemas_pydantic/utils/constants.py index 8b9e439..2a4b64a 100644 --- a/openapi_schemas_pydantic/utils/constants.py +++ b/openapi_schemas_pydantic/utils/constants.py @@ -24,7 +24,6 @@ NegativeInt, NonNegativeInt, NonPositiveFloat, - PaymentCardNumber, PositiveFloat, PositiveInt, PostgresDsn, @@ -50,6 +49,7 @@ SHAPE_TUPLE, SHAPE_TUPLE_ELLIPSIS, ) +from pydantic_extra_types.payment import PaymentCardNumber from openapi_schemas_pydantic.utils.enums import OpenAPIFormat, OpenAPIType from openapi_schemas_pydantic.v3_1_0.schema import Schema diff --git a/pyproject.toml b/pyproject.toml index e1a29ef..1d94a22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,11 @@ classifiers = [ "Typing :: Typed", ] -dependencies = ["pydantic>=2.0.0,<3.0.0", "email-validator >=1.2.1,<2.0.0"] +dependencies = [ + "pydantic>=2.0.0,<3.0.0", + "pydantic-extra-types>=2.0.0,<3.0.0", + "email-validator >=1.2.1,<2.0.0", +] [project.urls] Homepage = "https://github.com/dymmond/openapi-schemas-pydantic" From 398d942ed538ecbae0b0f392bc074f57d1bf6683 Mon Sep 17 00:00:00 2001 From: tarsil Date: Tue, 4 Jul 2023 10:39:06 +0100 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=AA=9B=20Fields=20shape=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi_schemas_pydantic/utils/constants.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openapi_schemas_pydantic/utils/constants.py b/openapi_schemas_pydantic/utils/constants.py index 2a4b64a..4af8459 100644 --- a/openapi_schemas_pydantic/utils/constants.py +++ b/openapi_schemas_pydantic/utils/constants.py @@ -1,6 +1,6 @@ from collections import deque from datetime import date, datetime, time, timedelta -from typing import Any, Dict, Pattern, Type, Union +from typing import Any, Callable, Dict, Pattern, Type, Union from uuid import UUID from pydantic import ( @@ -27,7 +27,6 @@ PositiveFloat, PositiveInt, PostgresDsn, - PyObject, RedisDsn, SecretBytes, SecretStr, @@ -37,7 +36,7 @@ StrictInt, StrictStr, ) -from pydantic.fields import ( +from pydantic.v1.fields import ( SHAPE_DEFAULTDICT, SHAPE_DEQUE, SHAPE_DICT, @@ -54,6 +53,8 @@ from openapi_schemas_pydantic.utils.enums import OpenAPIFormat, OpenAPIType from openapi_schemas_pydantic.v3_1_0.schema import Schema +PyObject = Callable[..., Any] + PYDANTIC_FIELD_SHAPE_MAP: Dict[int, OpenAPIType] = { SHAPE_LIST: OpenAPIType.ARRAY, SHAPE_SET: OpenAPIType.ARRAY,