diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..73f69f7
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,23 @@
+{
+ "name": "openapi-pydantic",
+ "image": "mcr.microsoft.com/devcontainers/python:0-3.11",
+ "features": {
+ "ghcr.io/devcontainers-contrib/features/poetry:2": {
+ "version": "latest"
+ }
+ },
+ "containerEnv": {
+ "POETRY_VIRTUALENVS_IN_PROJECT": "true"
+ },
+ "postCreateCommand": "poetry install && pip install --upgrade tox",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-python.python",
+ "ms-python.vscode-pylance",
+ "ms-python.black-formatter",
+ "charliermarsh.ruff"
+ ]
+ }
+ }
+}
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..6cd3771
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,24 @@
+changelog:
+ exclude:
+ labels:
+ - ignore-for-release
+ categories:
+ - title: Breaking 💥
+ labels:
+ - breaking
+ - title: Added 🎉
+ labels:
+ - feature
+ - title: Changed 🛠
+ labels:
+ - change
+ - title: Fixed 🐛
+ labels:
+ - fix
+ - bug
+ - title: Dependencies 📦
+ labels:
+ - dependencies
+ - title: Docs 📝
+ labels:
+ - documentation
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..69cfe2e
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,69 @@
+name: Publish
+
+on:
+ workflow_dispatch:
+ release:
+ types:
+ - published
+
+jobs:
+ build:
+ name: Build package
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.12
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ virtualenvs-create: false
+ version: 1.8.3
+ - name: Build package distribution
+ run: poetry build
+ - name: Upload package artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: distribution
+ path: dist
+ test-release:
+ name: Publish release to Test PyPI
+ if: github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: test
+ url: https://test.pypi.org/p/openapi-pydantic
+ permissions:
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
+ steps:
+ - name: Download package distribution
+ uses: actions/download-artifact@v4
+ with:
+ name: distribution
+ path: dist
+ - name: Publish package distribution to Test PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
+ release:
+ name: Publish release to PyPI
+ if: github.event_name == 'release'
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: production
+ url: https://pypi.org/p/openapi-pydantic
+ permissions:
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
+ steps:
+ - name: Download package distribution
+ uses: actions/download-artifact@v4
+ with:
+ name: distribution
+ path: dist
+ - name: Publish package distribution to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..24382aa
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,33 @@
+name: Test
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ tox:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install tox tox-gh-actions
+ - name: Install Poetry
+ uses: snok/install-poetry@v1
+ with:
+ version: 1.8.3
+ virtualenvs-create: true
+ virtualenvs-in-project: true
+ - name: Run tox test suite
+ run: tox
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a98b534
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+__pycache__
+/.idea
+/.mypy_cache
+/.pytest_cache
+/.ruff_cache
+/.tox
+/.venv
+/*.egg-info
+/build
+/dist
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..1b9ca3f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,16 @@
+{
+ "python.testing.pytestArgs": [
+ "tests",
+ "-vv"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "python.analysis.typeCheckingMode": "basic",
+ "python.linting.enabled": true,
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit",
+ "source.organizeImports": "explicit"
+ },
+ "editor.defaultFormatter": "ms-python.black-formatter"
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..4bca1da
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,42 @@
+# OpenAPI Pydantic Contribution Guide
+
+We welcome all contributions!
+
+## Issues
+
+Questions, feature requests and bug reports are all welcome as issues. When raising a bug or
+question, please include as much information as possible including the specific version you
+are using.
+
+## Pull Requests
+
+It should be very simple to get started and open a pull request, however for anything non-trivial
+please open an issue to discuss your intended change _before_ creating your PR. This avoids wasting
+time by ensuring that your changes will be accepted with fewer revisions down the line!
+
+### Local Development
+
+A [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) configuration is provided in the repo to get your environment setup automatically. Alternatively you can install [tox](https://tox.wiki/en/latest/) and [poetry](https://python-poetry.org/) manually.
+
+### Testing
+
+Please ensure all changes have good test coverage and are formatted correctly. You can run the test
+suite and linters using [tox](https://tox.wiki/en/latest/) - just run `tox` from the root of this
+repo to run the checks. These will also be run automatically in CI once your PR is opened. Don't
+worry about testing against every Ptyhon version - the CI action will do this for you!
+
+### Tagging
+
+When your PR is ready, please tag it with the appropriate tags: one of `feature`, `change`, `fix`,
+as well as `breaking` if you've introduced backwards-incompatible changes to the public API or
+behaviour.
+
+## Review
+
+We'll review your PR as soon as possible - either approving or requesting changes. Once the PR is
+approved, it will be merged into main and cut into the next release.
+
+## Releases
+
+The release schedule is not set in stone and will depend on the number of changes in flight, but where possible we'll look to cut a release with your changes as soon as possible. Once a new version is tagged,
+a package version is uploaded to PyPI automatically.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..04d2e21
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,40 @@
+MIT License
+
+Copyright (c) 2023 mike-oakley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----
+
+Forked from the original implementation by Kuimono, under MIT licence, from the repository at:
+https://github.com/kuimono/openapi-schema-pydantic
+
+Copyright (c) 2020 Kuimono
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+License URL: https://github.com/kuimono/openapi-schema-pydantic/blob/master/LICENSE
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ca2bc17
--- /dev/null
+++ b/README.md
@@ -0,0 +1,322 @@
+# openapi-pydantic
+
+[](https://pypi.org/project/openapi-pydantic/)
+[](https://github.com/mike-oakley/openapi-pydantic/blob/main/LICENSE)
+
+OpenAPI schema implemented in [Pydantic](https://github.com/samuelcolvin/pydantic). Both Pydantic 1.8+ and 2.x are supported.
+
+The naming of the classes follows the schema in
+[OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.1.md#schema).
+
+> This library is forked from [OpenAPI Schema Pydantic](https://github.com/kuimono/openapi-schema-pydantic) (at version [1.2.4](https://github.com/kuimono/openapi-schema-pydantic/releases/tag/v1.2.4)) which is no longer actively maintained.
+
+## Installation
+
+`pip install openapi-pydantic`
+
+## Try me
+
+```python
+from openapi_pydantic import OpenAPI, Info, PathItem, Operation, Response
+
+# Construct OpenAPI by pydantic objects
+open_api = OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ get=Operation(
+ responses={
+ "200": Response(
+ description="pong"
+ )
+ }
+ )
+ )
+ },
+)
+# Note: for Pydantic 1.x, replace `model_dump_json` with `json`
+print(open_api.model_dump_json(by_alias=True, exclude_none=True, indent=2))
+```
+
+Result:
+
+```json
+{
+ "openapi": "3.1.1",
+ "info": {
+ "title": "My own API",
+ "version": "v0.0.1"
+ },
+ "servers": [
+ {
+ "url": "/"
+ }
+ ],
+ "paths": {
+ "/ping": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "pong"
+ }
+ },
+ "deprecated": false
+ }
+ }
+ }
+}
+```
+
+## Take advantage of Pydantic
+
+Pydantic is a great tool. It allows you to use object / dict / mixed data for input.
+
+The following examples give the same OpenAPI result as above:
+
+```python
+from openapi_pydantic import parse_obj, OpenAPI, PathItem, Response
+
+# Construct OpenAPI from dict, inferring the correct schema version
+open_api = parse_obj({
+ "openapi": "3.1.1",
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {
+ "get": {"responses": {"200": {"description": "pong"}}}
+ }
+ },
+})
+
+
+# Construct OpenAPI v3.1 schema from dict
+# Note: for Pydantic 1.x, replace `model_validate` with `parse_obj`
+open_api = OpenAPI.model_validate({
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {
+ "get": {"responses": {"200": {"description": "pong"}}}
+ }
+ },
+})
+
+# Construct OpenAPI with mix of dict/object
+# Note: for Pydantic 1.x, replace `model_validate` with `parse_obj`
+open_api = OpenAPI.model_validate({
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": PathItem(
+ get={"responses": {"200": Response(description="pong")}}
+ )
+ },
+})
+```
+
+## Use Pydantic classes as schema
+
+- The [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.4.md#schemaObject)
+ in OpenAPI has definitions and tweaks in JSON Schema, which are hard to comprehend and define a good data class
+- Pydantic already has a good way to [create JSON schema](https://pydantic-docs.helpmanual.io/usage/schema/).
+ Let's not reinvent the wheel.
+
+The approach to deal with this:
+
+1. Use `PydanticSchema` objects to represent the `Schema` in `OpenAPI` object
+2. Invoke `construct_open_api_with_schema_class` to resolve the JSON schemas and references
+
+```python
+from pydantic import BaseModel, Field
+
+from openapi_pydantic import OpenAPI
+from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
+
+def construct_base_open_api() -> OpenAPI:
+ # Note: for Pydantic 1.x, replace `model_validate` with `parse_obj`
+ return OpenAPI.model_validate({
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {
+ "post": {
+ "requestBody": {"content": {"application/json": {
+ "schema": PydanticSchema(schema_class=PingRequest)
+ }}},
+ "responses": {"200": {
+ "description": "pong",
+ "content": {"application/json": {
+ "schema": PydanticSchema(schema_class=PingResponse)
+ }},
+ }},
+ }
+ }
+ },
+ })
+
+class PingRequest(BaseModel):
+ """Ping Request"""
+ req_foo: str = Field(description="foo value of the request")
+ req_bar: str = Field(description="bar value of the request")
+
+class PingResponse(BaseModel):
+ """Ping response"""
+ resp_foo: str = Field(description="foo value of the response")
+ resp_bar: str = Field(description="bar value of the response")
+
+open_api = construct_base_open_api()
+open_api = construct_open_api_with_schema_class(open_api)
+
+# print the result openapi.json
+# Note: for Pydantic 1.x, replace `model_dump_json` with `json`
+print(open_api.model_dump_json(by_alias=True, exclude_none=True, indent=2))
+```
+
+Result:
+
+```json
+{
+ "openapi": "3.1.1",
+ "info": {
+ "title": "My own API",
+ "version": "v0.0.1"
+ },
+ "servers": [
+ {
+ "url": "/"
+ }
+ ],
+ "paths": {
+ "/ping": {
+ "post": {
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PingRequest"
+ }
+ }
+ },
+ "required": false
+ },
+ "responses": {
+ "200": {
+ "description": "pong",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PingResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PingRequest": {
+ "title": "PingRequest",
+ "required": [
+ "req_foo",
+ "req_bar"
+ ],
+ "type": "object",
+ "properties": {
+ "req_foo": {
+ "title": "Req Foo",
+ "type": "string",
+ "description": "foo value of the request"
+ },
+ "req_bar": {
+ "title": "Req Bar",
+ "type": "string",
+ "description": "bar value of the request"
+ }
+ },
+ "description": "Ping Request"
+ },
+ "PingResponse": {
+ "title": "PingResponse",
+ "required": [
+ "resp_foo",
+ "resp_bar"
+ ],
+ "type": "object",
+ "properties": {
+ "resp_foo": {
+ "title": "Resp Foo",
+ "type": "string",
+ "description": "foo value of the response"
+ },
+ "resp_bar": {
+ "title": "Resp Bar",
+ "type": "string",
+ "description": "bar value of the response"
+ }
+ },
+ "description": "Ping response"
+ }
+ }
+ }
+}
+```
+
+## Notes
+
+### Use of OpenAPI.model_dump() / OpenAPI.model_dump_json() / OpenAPI.json() / OpenAPI.dict()
+
+When using `OpenAPI.model_dump()` / `OpenAPI.model_dump_json()` / `OpenAPI.json()` / `OpenAPI.dict()` functions,
+the arguments `by_alias=True, exclude_none=True` have to be in place.
+Otherwise the resulting json will not fit the OpenAPI standard.
+
+```python
+# OK (Pydantic 2)
+open_api.model_dump_json(by_alias=True, exclude_none=True, indent=2)
+# OK (Pydantic 1)
+open_api.json(by_alias=True, exclude_none=True, indent=2)
+
+# Not good
+open_api.model_dump_json(indent=2)
+open_api.json(indent=2)
+```
+
+More info about field aliases:
+
+| OpenAPI version | Field alias info |
+| --------------- | ---------------- |
+| 3.1 | [here](https://github.com/mike-oakley/openapi-pydantic/blob/main/openapi_pydantic/v3/v3_1/README.md#alias) |
+| 3.0 | [here](https://github.com/mike-oakley/openapi-pydantic/blob/main/openapi_pydantic/v3/v3_0/README.md#alias) |
+
+### Non-pydantic schema types
+
+Some schema types are not implemented as pydantic classes.
+Please refer to the following for more info:
+
+| OpenAPI version | Non-pydantic schema type info |
+| --------------- | ----------------------------- |
+| 3.1 | [here](https://github.com/mike-oakley/openapi-pydantic/blob/main/openapi_pydantic/v3/v3_1/README.md#non-pydantic-schema-types) |
+| 3.0 | [here](https://github.com/mike-oakley/openapi-pydantic/blob/main/openapi_pydantic/v3/v3_0/README.md#non-pydantic-schema-types) |
+
+### Use OpenAPI 3.0 instead of 3.1
+
+Some UI renderings (e.g. Swagger) still do not support OpenAPI 3.1.x.
+The old 3.0.x version is available by importing from different paths:
+
+```python
+from openapi_pydantic.v3.v3_0 import OpenAPI, ...
+from openapi_pydantic.v3.v3_0.util import PydanticSchema, construct_open_api_with_schema_class
+```
+
+### Pydantic version compatibility
+
+Compatibility with both major versions of Pydantic (1.8+ and 2.*) is mostly achieved using a module called `compat.py`. It detects the installed version of Pydantic and exports version-specific symbols for use by the rest of the package. It also provides all symbols necessary for type checking. The `compat.py` module is not intended to be imported by other packages, but other packages may find it helpful as an example of how to span major versions of Pydantic.
+
+## Credits
+
+This library is based from the original implementation by Kuimono of [OpenAPI Schema Pydantic](https://github.com/kuimono/openapi-schema-pydantic) which is no longer actively maintained.
+
+## License
+
+[MIT License](https://github.com/mike-oakley/openapi-pydantic/blob/main/LICENSE)
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..1103077
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+As we're a small project and have limited maintainer capacity, only the latest major version of OpenAPI Pydantic is actively maintained. Whilst
+we are pre-v1, this extends to the latest minor version - so only the latest 0.x version is actively maintained. Please update to the latest available
+version before reporting bugs or security vulnerabilities.
+
+## Reporting a Vulnerability
+
+To report a vulnerability associated with this project, use the [GitHub vulnerability reporting tool](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability).
+Head over to the [Security](https://github.com/mike-oakley/openapi-pydantic/security) tab
+and click **Report a vulnerability** to open the advisory form.
diff --git a/openapi_pydantic/__init__.py b/openapi_pydantic/__init__.py
new file mode 100644
index 0000000..23339dc
--- /dev/null
+++ b/openapi_pydantic/__init__.py
@@ -0,0 +1,38 @@
+import logging
+
+from .v3 import XML as XML
+from .v3 import Callback as Callback
+from .v3 import Components as Components
+from .v3 import Contact as Contact
+from .v3 import DataType as DataType
+from .v3 import Discriminator as Discriminator
+from .v3 import Encoding as Encoding
+from .v3 import Example as Example
+from .v3 import ExternalDocumentation as ExternalDocumentation
+from .v3 import Header as Header
+from .v3 import Info as Info
+from .v3 import License as License
+from .v3 import Link as Link
+from .v3 import MediaType as MediaType
+from .v3 import OAuthFlow as OAuthFlow
+from .v3 import OAuthFlows as OAuthFlows
+from .v3 import OpenAPI as OpenAPI
+from .v3 import Operation as Operation
+from .v3 import Parameter as Parameter
+from .v3 import ParameterLocation as ParameterLocation
+from .v3 import PathItem as PathItem
+from .v3 import Paths as Paths
+from .v3 import Reference as Reference
+from .v3 import RequestBody as RequestBody
+from .v3 import Response as Response
+from .v3 import Responses as Responses
+from .v3 import Schema as Schema
+from .v3 import SecurityRequirement as SecurityRequirement
+from .v3 import SecurityScheme as SecurityScheme
+from .v3 import Server as Server
+from .v3 import ServerVariable as ServerVariable
+from .v3 import Tag as Tag
+from .v3 import parse_obj as parse_obj
+from .v3 import schema_validate as schema_validate
+
+logging.getLogger(__name__).addHandler(logging.NullHandler())
diff --git a/openapi_pydantic/compat.py b/openapi_pydantic/compat.py
new file mode 100644
index 0000000..840089b
--- /dev/null
+++ b/openapi_pydantic/compat.py
@@ -0,0 +1,119 @@
+"""Compatibility layer to make this package usable with Pydantic 1 or 2"""
+
+from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
+
+from pydantic.version import VERSION as PYDANTIC_VERSION
+
+__all__ = [
+ "PYDANTIC_V2",
+ "ConfigDict",
+ "JsonSchemaMode",
+ "models_json_schema",
+ "RootModel",
+ "Extra",
+ "v1_schema",
+ "DEFS_KEY",
+ "min_length_arg",
+]
+
+PYDANTIC_MAJOR_VERSION = int(PYDANTIC_VERSION.split(".", 1)[0])
+PYDANTIC_MINOR_VERSION = int(PYDANTIC_VERSION.split(".")[1])
+PYDANTIC_V2 = PYDANTIC_MAJOR_VERSION >= 2
+
+if TYPE_CHECKING:
+ # Provide stubs for either version of Pydantic
+
+ from enum import Enum
+ from typing import Any, Literal, Type, TypedDict
+
+ from pydantic import BaseModel
+ from pydantic import ConfigDict as PydanticConfigDict
+
+ def ConfigDict(
+ extra: Literal["allow", "ignore", "forbid"] = "allow",
+ json_schema_extra: Optional[Dict[str, Any]] = None,
+ populate_by_name: bool = True,
+ ) -> PydanticConfigDict:
+ """Stub for pydantic.ConfigDict in Pydantic 2"""
+ ...
+
+ class Extra(Enum):
+ """Stub for pydantic.Extra in Pydantic 1"""
+
+ allow = "allow"
+ ignore = "ignore"
+ forbid = "forbid"
+
+ class RootModel(BaseModel):
+ """Stub for pydantic.RootModel in Pydantic 2"""
+
+ JsonSchemaMode = Literal["validation", "serialization"]
+
+ def models_json_schema(
+ models: List[Tuple[Type[BaseModel], JsonSchemaMode]],
+ *,
+ by_alias: bool = True,
+ ref_template: str = "#/$defs/{model}",
+ schema_generator: Optional[type] = None,
+ ) -> Tuple[Dict, Dict[str, Any]]:
+ """Stub for pydantic.json_schema.models_json_schema in Pydantic 2"""
+ ...
+
+ def v1_schema(
+ models: List[Type[BaseModel]],
+ *,
+ by_alias: bool = True,
+ ref_prefix: str = "#/$defs",
+ ) -> Dict[str, Any]:
+ """Stub for pydantic.schema.schema in Pydantic 1"""
+ ...
+
+ DEFS_KEY = "$defs"
+
+ class MinLengthArg(TypedDict):
+ pass
+
+ def min_length_arg(min_length: int) -> MinLengthArg:
+ """Generate a min_length or min_items parameter for Field(...)"""
+ ...
+
+elif PYDANTIC_V2:
+ from typing import TypedDict
+
+ from pydantic import ConfigDict, RootModel
+ from pydantic.json_schema import JsonSchemaMode, models_json_schema
+
+ # Pydantic 2 renders JSON schemas using the keyword "$defs"
+ DEFS_KEY = "$defs"
+
+ class MinLengthArg(TypedDict):
+ min_length: int
+
+ def min_length_arg(min_length: int) -> MinLengthArg:
+ return {"min_length": min_length}
+
+ # Create V1 stubs. These should not be used when PYDANTIC_V2 is true.
+ Extra = None
+ v1_schema = None
+
+
+else:
+ from typing import TypedDict
+
+ from pydantic import Extra
+ from pydantic.schema import schema as v1_schema
+
+ # Pydantic 1 renders JSON schemas using the keyword "definitions"
+ DEFS_KEY = "definitions"
+
+ class MinLengthArg(TypedDict):
+ min_items: int
+
+ def min_length_arg(min_length: int) -> MinLengthArg:
+ return {"min_items": min_length}
+
+ # Create V2 stubs. These should not be used when PYDANTIC_V2 is false.
+ ConfigDict = None
+ models_json_schema = None
+ JsonSchemaMode = None
+ RootModel = None
diff --git a/openapi_pydantic/py.typed b/openapi_pydantic/py.typed
new file mode 100644
index 0000000..7632ecf
--- /dev/null
+++ b/openapi_pydantic/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561
diff --git a/openapi_pydantic/util.py b/openapi_pydantic/util.py
new file mode 100644
index 0000000..62aeb89
--- /dev/null
+++ b/openapi_pydantic/util.py
@@ -0,0 +1,188 @@
+import logging
+import re
+from typing import Any, Dict, Generic, List, Optional, Set, Type, TypeVar, cast
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import (
+ DEFS_KEY,
+ PYDANTIC_V2,
+ JsonSchemaMode,
+ models_json_schema,
+ v1_schema,
+)
+
+from . import Components, OpenAPI, Reference, Schema, schema_validate
+
+logger = logging.getLogger(__name__)
+
+PydanticType = TypeVar("PydanticType", bound=BaseModel)
+ref_prefix = "#/components/schemas/"
+ref_template = "#/components/schemas/{model}"
+
+
+class PydanticSchema(Schema, Generic[PydanticType]):
+ """Special `Schema` class to indicate a reference from pydantic class"""
+
+ schema_class: Type[PydanticType]
+ """the class that is used for generate the schema"""
+
+
+def get_mode(
+ cls: Type[BaseModel], default: JsonSchemaMode = "validation"
+) -> JsonSchemaMode:
+ """Get the JSON schema mode for a model class.
+
+ The mode can be either "validation" or "serialization". In validation mode,
+ computed fields are dropped and optional fields remain optional. In
+ serialization mode, computed and optional fields are required.
+ """
+ if not hasattr(cls, "model_config"):
+ return default
+ mode = cls.model_config.get("json_schema_mode", default)
+ if mode not in ("validation", "serialization"):
+ raise ValueError(f"invalid json_schema_mode: {mode}")
+ return cast(JsonSchemaMode, mode)
+
+
+def construct_open_api_with_schema_class(
+ open_api: OpenAPI,
+ schema_classes: Optional[List[Type[BaseModel]]] = None,
+ scan_for_pydantic_schema_reference: bool = True,
+ by_alias: bool = True,
+) -> OpenAPI:
+ """
+ Construct a new OpenAPI object, utilising pydantic classes to produce JSON schemas.
+
+ :param open_api: the base `OpenAPI` object
+ :param schema_classes: Pydantic classes that their schema will be used
+ "#/components/schemas" values
+ :param scan_for_pydantic_schema_reference: flag to indicate if scanning for
+ `PydanticSchemaReference` class
+ is needed for "#/components/schemas"
+ value updates
+ :param by_alias: construct schema by alias (default is True)
+ :return: new OpenAPI object with "#/components/schemas" values updated.
+ If there is no update in "#/components/schemas" values, the original
+ `open_api` will be returned.
+ """
+ copy_func = getattr(open_api, "model_copy" if PYDANTIC_V2 else "copy")
+ new_open_api: OpenAPI = copy_func(deep=True)
+
+ if scan_for_pydantic_schema_reference:
+ extracted_schema_classes = _handle_pydantic_schema(new_open_api)
+ if schema_classes:
+ schema_classes = list({*schema_classes, *extracted_schema_classes})
+ else:
+ schema_classes = extracted_schema_classes
+
+ if not schema_classes:
+ return open_api
+
+ schema_classes.sort(key=lambda x: x.__name__)
+ logger.debug("schema_classes: %s", schema_classes)
+
+ # update new_open_api with new #/components/schemas
+ if PYDANTIC_V2:
+ _key_map, schema_definitions = models_json_schema(
+ [(c, get_mode(c)) for c in schema_classes],
+ by_alias=by_alias,
+ ref_template=ref_template,
+ )
+ else:
+ schema_definitions = v1_schema(
+ schema_classes, by_alias=by_alias, ref_prefix=ref_prefix
+ )
+
+ if not new_open_api.components:
+ new_open_api.components = Components()
+ if new_open_api.components.schemas:
+ for existing_key in new_open_api.components.schemas:
+ if existing_key in schema_definitions[DEFS_KEY]:
+ logger.warning(
+ f'"{existing_key}" already exists in {ref_prefix}. '
+ f'The value of "{ref_prefix}{existing_key}" will be overwritten.'
+ )
+ new_open_api.components.schemas.update(_validate_schemas(schema_definitions))
+ else:
+ new_open_api.components.schemas = _validate_schemas(schema_definitions)
+ return new_open_api
+
+
+def _validate_schemas(schema_definitions: Dict[str, Any]) -> Dict[str, Schema]:
+ """Convert JSON Schema definitions to parsed OpenAPI objects"""
+ # Note: if an error occurs in schema_validate(), it may indicate that
+ # the generated JSON schemas are not compatible with the version
+ # of OpenAPI this module depends on.
+ return {
+ key: schema_validate(schema_dict)
+ for key, schema_dict in schema_definitions[DEFS_KEY].items()
+ }
+
+
+def _handle_pydantic_schema(open_api: OpenAPI) -> List[Type[BaseModel]]:
+ """
+ This function traverses the `OpenAPI` object and
+
+ 1. Replaces the `PydanticSchema` object with `Reference` object, with correct ref
+ value;
+ 2. Extracts the involved schema class from `PydanticSchema` object.
+
+ **This function will mutate the input `OpenAPI` object.**
+
+ :param open_api: the `OpenAPI` object to be traversed and mutated
+ :return: a list of schema classes extracted from `PydanticSchema` objects
+ """
+
+ pydantic_types: Set[Type[BaseModel]] = set()
+
+ def _traverse(obj: Any) -> None:
+ if isinstance(obj, BaseModel):
+ fields = getattr(
+ obj, "model_fields_set" if PYDANTIC_V2 else "__fields_set__"
+ )
+ for field in fields:
+ child_obj = obj.__getattribute__(field)
+ if isinstance(child_obj, PydanticSchema):
+ logger.debug("PydanticSchema found in %s: %s", obj, child_obj)
+ obj.__setattr__(field, _construct_ref_obj(child_obj))
+ pydantic_types.add(child_obj.schema_class)
+ else:
+ _traverse(child_obj)
+ elif isinstance(obj, list):
+ for index, elem in enumerate(obj):
+ if isinstance(elem, PydanticSchema):
+ logger.debug(f"PydanticSchema found in list: {elem}")
+ obj[index] = _construct_ref_obj(elem)
+ pydantic_types.add(elem.schema_class)
+ else:
+ _traverse(elem)
+ elif isinstance(obj, dict):
+ for key, value in obj.items():
+ if isinstance(value, PydanticSchema):
+ logger.debug(f"PydanticSchema found in dict: {value}")
+ obj[key] = _construct_ref_obj(value)
+ pydantic_types.add(value.schema_class)
+ else:
+ _traverse(value)
+
+ _traverse(open_api)
+ return list(pydantic_types)
+
+
+def _construct_ref_obj(pydantic_schema: PydanticSchema[PydanticType]) -> Reference:
+ """
+ Construct a reference object from the Pydantic schema name
+
+ characters in the schema name that are invalid/problematic
+ for JSONschema $ref names will get replaced with underscores.
+ Especially needed for Pydantic generic Models with brackets "[]"
+
+ see: https://github.com/pydantic/pydantic/blob/aee6057378ccfec02126bf9c984a9b6d6b411777/pydantic/json_schema.py#L2031
+ """
+ ref_name = re.sub(
+ r"[^a-zA-Z0-9.\-_]", "_", pydantic_schema.schema_class.__name__
+ ).replace(".", "__")
+ ref_obj = Reference(**{"$ref": ref_prefix + ref_name})
+ logger.debug(f"ref_obj={ref_obj}")
+ return ref_obj
diff --git a/openapi_pydantic/v3/__init__.py b/openapi_pydantic/v3/__init__.py
new file mode 100644
index 0000000..ee575e8
--- /dev/null
+++ b/openapi_pydantic/v3/__init__.py
@@ -0,0 +1,34 @@
+from .parser import parse_obj as parse_obj
+from .v3_1 import XML as XML
+from .v3_1 import Callback as Callback
+from .v3_1 import Components as Components
+from .v3_1 import Contact as Contact
+from .v3_1 import DataType as DataType
+from .v3_1 import Discriminator as Discriminator
+from .v3_1 import Encoding as Encoding
+from .v3_1 import Example as Example
+from .v3_1 import ExternalDocumentation as ExternalDocumentation
+from .v3_1 import Header as Header
+from .v3_1 import Info as Info
+from .v3_1 import License as License
+from .v3_1 import Link as Link
+from .v3_1 import MediaType as MediaType
+from .v3_1 import OAuthFlow as OAuthFlow
+from .v3_1 import OAuthFlows as OAuthFlows
+from .v3_1 import OpenAPI as OpenAPI
+from .v3_1 import Operation as Operation
+from .v3_1 import Parameter as Parameter
+from .v3_1 import ParameterLocation as ParameterLocation
+from .v3_1 import PathItem as PathItem
+from .v3_1 import Paths as Paths
+from .v3_1 import Reference as Reference
+from .v3_1 import RequestBody as RequestBody
+from .v3_1 import Response as Response
+from .v3_1 import Responses as Responses
+from .v3_1 import Schema as Schema
+from .v3_1 import SecurityRequirement as SecurityRequirement
+from .v3_1 import SecurityScheme as SecurityScheme
+from .v3_1 import Server as Server
+from .v3_1 import ServerVariable as ServerVariable
+from .v3_1 import Tag as Tag
+from .v3_1 import schema_validate as schema_validate
diff --git a/openapi_pydantic/v3/parser.py b/openapi_pydantic/v3/parser.py
new file mode 100644
index 0000000..0149bff
--- /dev/null
+++ b/openapi_pydantic/v3/parser.py
@@ -0,0 +1,33 @@
+from typing import TYPE_CHECKING, Any, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2
+
+from .v3_0 import OpenAPI as OpenAPIv3_0
+from .v3_1 import OpenAPI as OpenAPIv3_1
+
+OpenAPIv3 = Union[OpenAPIv3_1, OpenAPIv3_0]
+
+if TYPE_CHECKING:
+
+ def parse_obj(data: Any) -> OpenAPIv3:
+ """Parse a raw object into an OpenAPI model with version inference."""
+ ...
+
+elif PYDANTIC_V2:
+ from pydantic import RootModel
+
+ class _OpenAPI(RootModel):
+ root: OpenAPIv3 = Field(discriminator="openapi")
+
+ def parse_obj(data: Any) -> OpenAPIv3:
+ return _OpenAPI.model_validate(data).root
+
+else:
+
+ class _OpenAPI(BaseModel):
+ __root__: OpenAPIv3 = Field(discriminator="openapi")
+
+ def parse_obj(data: Any) -> OpenAPIv3:
+ return _OpenAPI.parse_obj(data).__root__
diff --git a/openapi_pydantic/v3/v3_0/README.md b/openapi_pydantic/v3/v3_0/README.md
new file mode 100644
index 0000000..16b9c44
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/README.md
@@ -0,0 +1,39 @@
+# OpenAPI v3.0 schema classes
+
+## Alias
+
+Due to the reserved words in python and pydantic,
+the following fields are used with [alias](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) feature provided by pydantic:
+
+| Class | Field name in the class | Alias (as in OpenAPI spec) |
+| ----- | ----------------------- | -------------------------- |
+| Header[*](#header_param_in) | param_in | in |
+| MediaType | media_type_schema | schema |
+| Parameter | param_in | in |
+| Parameter | param_schema | schema |
+| PathItem | ref | $ref |
+| Reference | ref | $ref |
+| SecurityScheme | security_scheme_in | in |
+| Schema | schema_format | format |
+| Schema | schema_not | not |
+
+> The "in" field in Header object is actually a constant (`{"in": "header"}`).
+
+> For convenience of object creation, the classes mentioned in above
+> have configured `allow_population_by_field_name=True` (Pydantic V1) or `populate_by_name=True` (Pydantic V2).
+>
+> Reference: [Pydantic's Model Config](https://pydantic-docs.helpmanual.io/usage/model_config/)
+
+## Non-pydantic schema types
+
+Due to the constriants of python typing structure (not able to handle dynamic field names),
+the following schema classes are actually just a typing of `Dict`:
+
+| Schema Type | Implementation |
+| ----------- | -------------- |
+| Callback | `Callback = Dict[str, PathItem]` |
+| Paths | `Paths = Dict[str, PathItem]` |
+| Responses | `Responses = Dict[str, Union[Response, Reference]]` |
+| SecurityRequirement | `SecurityRequirement = Dict[str, List[str]]` |
+
+On creating such schema instances, please use python's `dict` type instead to instantiate.
diff --git a/openapi_pydantic/v3/v3_0/__init__.py b/openapi_pydantic/v3/v3_0/__init__.py
new file mode 100644
index 0000000..af250d4
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/__init__.py
@@ -0,0 +1,59 @@
+"""
+OpenAPI v3.0 schema types, created according to the specification:
+https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.4.md
+
+The type orders are according to the contents of the specification:
+https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.4.md#table-of-contents
+"""
+
+from typing import TYPE_CHECKING
+
+from openapi_pydantic.compat import PYDANTIC_V2
+
+from .callback import Callback as Callback
+from .components import Components as Components
+from .contact import Contact as Contact
+from .datatype import DataType as DataType
+from .discriminator import Discriminator as Discriminator
+from .encoding import Encoding as Encoding
+from .example import Example as Example
+from .external_documentation import ExternalDocumentation as ExternalDocumentation
+from .header import Header as Header
+from .info import Info as Info
+from .license import License as License
+from .link import Link as Link
+from .media_type import MediaType as MediaType
+from .oauth_flow import OAuthFlow as OAuthFlow
+from .oauth_flows import OAuthFlows as OAuthFlows
+from .open_api import OpenAPI as OpenAPI
+from .operation import Operation as Operation
+from .parameter import Parameter as Parameter
+from .parameter import ParameterLocation as ParameterLocation
+from .path_item import PathItem as PathItem
+from .paths import Paths as Paths
+from .reference import Reference as Reference
+from .request_body import RequestBody as RequestBody
+from .response import Response as Response
+from .responses import Responses as Responses
+from .schema import Schema as Schema
+from .schema import schema_validate as schema_validate
+from .security_requirement import SecurityRequirement as SecurityRequirement
+from .security_scheme import SecurityScheme as SecurityScheme
+from .server import Server as Server
+from .server_variable import ServerVariable as ServerVariable
+from .tag import Tag as Tag
+from .xml import XML as XML
+
+if TYPE_CHECKING:
+ pass
+elif PYDANTIC_V2:
+ # resolve forward references
+ Encoding.model_rebuild()
+ OpenAPI.model_rebuild()
+ Components.model_rebuild()
+ Operation.model_rebuild()
+else:
+ # resolve forward references
+ Encoding.update_forward_refs(Header=Header)
+ Schema.update_forward_refs()
+ Operation.update_forward_refs(PathItem=PathItem)
diff --git a/openapi_pydantic/v3/v3_0/callback.py b/openapi_pydantic/v3/v3_0/callback.py
new file mode 100644
index 0000000..bd9a0bc
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/callback.py
@@ -0,0 +1,24 @@
+from typing import TYPE_CHECKING, Dict
+
+if TYPE_CHECKING:
+ from .path_item import PathItem
+
+
+Callback = Dict[str, "PathItem"]
+"""
+A map of possible out-of band callbacks related to the parent operation.
+Each value in the map is a [Path Item Object](#pathItemObject)
+that describes a set of requests that may be initiated by the API provider and the
+expected responses. The key value used to identify the path item object is an
+expression, evaluated at runtime, that identifies a URL to use for the callback
+operation.
+"""
+
+"""Patterned Fields"""
+
+# {expression}: 'PathItem' = ...
+"""
+A Path Item Object used to define a callback request and expected responses.
+
+A [complete example](../examples/v3.0/callback-example.yaml) is available.
+"""
diff --git a/openapi_pydantic/v3/v3_0/components.py b/openapi_pydantic/v3/v3_0/components.py
new file mode 100644
index 0000000..7010e93
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/components.py
@@ -0,0 +1,138 @@
+from typing import Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .callback import Callback
+from .example import Example
+from .header import Header
+from .link import Link
+from .parameter import Parameter
+from .reference import Reference
+from .request_body import RequestBody
+from .response import Response
+from .schema import Schema
+from .security_scheme import SecurityScheme
+
+_examples = [
+ {
+ "schemas": {
+ "GeneralError": {
+ "type": "object",
+ "properties": {
+ "code": {"type": "integer", "format": "int32"},
+ "message": {"type": "string"},
+ },
+ },
+ "Category": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ },
+ "Tag": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ },
+ },
+ "parameters": {
+ "skipParam": {
+ "name": "skip",
+ "in": "query",
+ "description": "number of items to skip",
+ "required": True,
+ "schema": {"type": "integer", "format": "int32"},
+ },
+ "limitParam": {
+ "name": "limit",
+ "in": "query",
+ "description": "max records to return",
+ "required": True,
+ "schema": {"type": "integer", "format": "int32"},
+ },
+ },
+ "responses": {
+ "NotFound": {"description": "Entity not found."},
+ "IllegalInput": {"description": "Illegal input for operation."},
+ "GeneralError": {
+ "description": "General Error",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/GeneralError"}
+ }
+ },
+ },
+ },
+ "securitySchemes": {
+ "api_key": {
+ "type": "apiKey",
+ "name": "api_key",
+ "in": "header",
+ },
+ "petstore_auth": {
+ "type": "oauth2",
+ "flows": {
+ "implicit": {
+ "authorizationUrl": "http://example.org/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ }
+ },
+ },
+ },
+ }
+]
+
+
+class Components(BaseModel):
+ """
+ Holds a set of reusable objects for different aspects of the OAS.
+ All objects defined within the components object will have no effect on the API
+ unless they are explicitly referenced from properties outside the components object.
+ """
+
+ schemas: Optional[Dict[str, Union[Reference, Schema]]] = None
+ """An object to hold reusable [Schema Objects](#schemaObject)."""
+
+ responses: Optional[Dict[str, Union[Response, Reference]]] = None
+ """An object to hold reusable [Response Objects](#responseObject)."""
+
+ parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None
+ """An object to hold reusable [Parameter Objects](#parameterObject)."""
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """An object to hold reusable [Example Objects](#exampleObject)."""
+
+ requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None
+ """An object to hold reusable [Request Body Objects](#requestBodyObject)."""
+
+ headers: Optional[Dict[str, Union[Header, Reference]]] = None
+ """An object to hold reusable [Header Objects](#headerObject)."""
+
+ securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
+ """An object to hold reusable [Security Scheme Objects](#securitySchemeObject)."""
+
+ links: Optional[Dict[str, Union[Link, Reference]]] = None
+ """An object to hold reusable [Link Objects](#linkObject)."""
+
+ callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None
+ """An object to hold reusable [Callback Objects](#callbackObject)."""
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/contact.py b/openapi_pydantic/v3/v3_0/contact.py
new file mode 100644
index 0000000..9c76af6
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/contact.py
@@ -0,0 +1,48 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "name": "API Support",
+ "url": "http://www.example.com/support",
+ "email": "support@example.com",
+ }
+]
+
+
+class Contact(BaseModel):
+ """
+ Contact information for the exposed API.
+ """
+
+ name: Optional[str] = None
+ """
+ The identifying name of the contact person/organization.
+ """
+
+ url: Optional[str] = None
+ """
+ The URL pointing to the contact information.
+ MUST be in the format of a URL.
+ """
+
+ email: Optional[str] = None
+ """
+ The email address of the contact person/organization.
+ MUST be in the format of an email address.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/datatype.py b/openapi_pydantic/v3/v3_0/datatype.py
new file mode 100644
index 0000000..c62492a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/datatype.py
@@ -0,0 +1,15 @@
+import enum
+
+
+class DataType(str, enum.Enum):
+ """Data type of an object.
+
+ Note: OpenAPI 3.0.x does not support null as a data type.
+ """
+
+ STRING = "string"
+ NUMBER = "number"
+ INTEGER = "integer"
+ BOOLEAN = "boolean"
+ ARRAY = "array"
+ OBJECT = "object"
diff --git a/openapi_pydantic/v3/v3_0/discriminator.py b/openapi_pydantic/v3/v3_0/discriminator.py
new file mode 100644
index 0000000..1348906
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/discriminator.py
@@ -0,0 +1,52 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "#/components/schemas/Dog",
+ "monster": "https://gigantic-server.com/schemas/Monster/schema.json",
+ },
+ }
+]
+
+
+class Discriminator(BaseModel):
+ """
+ When request bodies or response payloads may be one of a number of different
+ schemas, a `discriminator` object can be used to aid in serialization,
+ deserialization, and validation.
+
+ The discriminator is a specific object in a schema which is used to inform the
+ consumer of the specification of an alternative schema based on the value
+ associated with it.
+
+ 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.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/encoding.py b/openapi_pydantic/v3/v3_0/encoding.py
new file mode 100644
index 0000000..1cbe6c9
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/encoding.py
@@ -0,0 +1,94 @@
+from typing import TYPE_CHECKING, Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .reference import Reference
+
+if TYPE_CHECKING:
+ from .header import Header
+
+_examples = [
+ {
+ "contentType": "image/png, image/jpeg",
+ "headers": {
+ "X-Rate-Limit-Limit": {
+ "description": "The number of allowed requests in the "
+ "current period",
+ "schema": {"type": "integer"},
+ }
+ },
+ }
+]
+
+
+class Encoding(BaseModel):
+ """A single encoding definition applied to a single schema property."""
+
+ contentType: Optional[str] = None
+ """
+ The Content-Type for encoding a specific property.
+ Default value depends on the property type:
+
+ - for `string` with `format` being `binary` – `application/octet-stream`;
+ - for other primitive types – `text/plain`;
+ - for `object` - `application/json`;
+ - for `array` – the default is defined based on the inner type.
+
+ The value can be a specific media type (e.g. `application/json`), a wildcard media
+ type (e.g. `image/*`), or a comma-separated list of the two types.
+ """
+
+ headers: Optional[Dict[str, Union["Header", Reference]]] = None
+ """
+ A map allowing additional information to be provided as headers, for example
+ `Content-Disposition`.
+
+ `Content-Type` is described separately and SHALL be ignored in this section.
+ This property SHALL be ignored if the request body media type is not a `multipart`.
+ """
+
+ style: Optional[str] = None
+ """
+ Describes how a specific property value will be serialized depending on its type.
+
+ See [Parameter Object](#parameterObject) for details on the
+ [`style`](#parameterStyle) property. The behavior follows the same values as
+ `query` parameters, including default values. This property SHALL be ignored if
+ the request body media type is not `application/x-www-form-urlencoded`.
+ """
+
+ explode: Optional[bool] = None
+ """
+ When this is true, property values of type `array` or `object` generate separate
+ parameters for each value of the array, or key-value-pair of the map.
+
+ For other types of properties this property has no effect.
+ When [`style`](#encodingStyle) is `form`, the default value is `true`.
+ For all other styles, the default value is `false`.
+ This property SHALL be ignored if the request body media type is not
+ `application/x-www-form-urlencoded`.
+ """
+
+ allowReserved: bool = False
+ """
+ Determines whether the parameter value SHOULD allow reserved characters,
+ as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
+ `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
+ The default value is `false`.
+ This property SHALL be ignored if the request body media type is not
+ `application/x-www-form-urlencoded`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/example.py b/openapi_pydantic/v3/v3_0/example.py
new file mode 100644
index 0000000..5820a17
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/example.py
@@ -0,0 +1,60 @@
+from typing import Any, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, 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",
+ },
+]
+
+
+class Example(BaseModel):
+ summary: Optional[str] = None
+ """
+ Short description for the example.
+ """
+
+ description: Optional[str] = None
+ """
+ Long description for the example.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ value: Optional[Any] = None
+ """
+ Embedded literal example.
+ The `value` field and `externalValue` field are mutually exclusive.
+ To represent examples of media types that cannot naturally represented in JSON or
+ YAML, use a string value to contain the example, escaping where necessary.
+ """
+
+ externalValue: Optional[str] = None
+ """
+ A URL that points to the literal example.
+ This provides the capability to reference examples that cannot easily be included
+ in JSON or YAML documents.
+
+ The `value` field and `externalValue` field are mutually exclusive.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/external_documentation.py b/openapi_pydantic/v3/v3_0/external_documentation.py
new file mode 100644
index 0000000..7d1faf8
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/external_documentation.py
@@ -0,0 +1,36 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [{"description": "Find more info here", "url": "https://example.com"}]
+
+
+class ExternalDocumentation(BaseModel):
+ """Allows referencing an external resource for extended documentation."""
+
+ description: Optional[str] = None
+ """
+ A short description of the target documentation.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ url: str
+ """
+ **REQUIRED**. The URL for the target documentation.
+ Value MUST be in the format of a URL.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/header.py b/openapi_pydantic/v3/v3_0/header.py
new file mode 100644
index 0000000..451d77e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/header.py
@@ -0,0 +1,37 @@
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .parameter import ParameterBase
+
+_examples = [
+ {
+ "description": "The number of allowed requests in the current period",
+ "schema": {"type": "integer"},
+ }
+]
+
+
+class Header(ParameterBase):
+ """
+ The Header Object follows the structure of the
+ [Parameter Object](#parameterObject) with the following changes:
+
+ 1. `name` MUST NOT be specified, it is given in the corresponding
+ `headers` map.
+ 2. `in` MUST NOT be specified, it is implicitly in `header`.
+ 3. All traits that are affected by the location MUST be applicable
+ to a location of `header` (for example, [`style`](#parameterStyle)).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/info.py b/openapi_pydantic/v3/v3_0/info.py
new file mode 100644
index 0000000..cc6986b
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/info.py
@@ -0,0 +1,81 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .contact import Contact
+from .license import License
+
+_examples = [
+ {
+ "title": "Sample Pet Store App",
+ "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",
+ }
+]
+
+
+class Info(BaseModel):
+ """
+ The object provides metadata about the API.
+ The metadata MAY be used by the clients if needed,
+ and MAY be presented in editing or documentation generation tools for convenience.
+ """
+
+ title: str
+ """
+ **REQUIRED**. The title of the API.
+ """
+
+ description: Optional[str] = None
+ """
+ A short description of the API.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ termsOfService: Optional[str] = None
+ """
+ A URL to the Terms of Service for the API.
+ MUST be in the format of a URL.
+ """
+
+ contact: Optional[Contact] = None
+ """
+ The contact information for the exposed API.
+ """
+
+ license: Optional[License] = None
+ """
+ The license information for the exposed API.
+ """
+
+ version: str
+ """
+ **REQUIRED**. The version of the OpenAPI document
+ (which is distinct from the [OpenAPI Specification version](#oasVersion) or the API
+ implementation version).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/license.py b/openapi_pydantic/v3/v3_0/license.py
new file mode 100644
index 0000000..fddd70c
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/license.py
@@ -0,0 +1,41 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "name": "Apache 2.0",
+ "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
+ }
+]
+
+
+class License(BaseModel):
+ """
+ License information for the exposed API.
+ """
+
+ name: str
+ """
+ **REQUIRED**. The license name used for the API.
+ """
+
+ url: Optional[str] = None
+ """
+ A URL to the license used for the API.
+ MUST be in the format of a URL.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/link.py b/openapi_pydantic/v3/v3_0/link.py
new file mode 100644
index 0000000..f73de4c
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/link.py
@@ -0,0 +1,94 @@
+from typing import Any, Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .server import Server
+
+_examples = [
+ {
+ "operationId": "getUserAddressByUUID",
+ "parameters": {"userUuid": "$response.body#/uuid"},
+ },
+ {
+ "operationRef": "#/paths/~12.0~1repositories~1{username}/get",
+ "parameters": {"username": "$response.body#/username"},
+ },
+]
+
+
+class Link(BaseModel):
+ """
+ The `Link object` represents a possible design-time link for a response.
+ The presence of a link does not guarantee the caller's ability to successfully
+ invoke it, rather it provides a known relationship and traversal mechanism between
+ responses and other operations.
+
+ Unlike _dynamic_ links (i.e. links provided **in** the response payload),
+ the OAS linking mechanism does not require link information in the runtime response.
+
+ For computing links, and providing instructions to execute them,
+ a [runtime expression](#runtimeExpression) is used for accessing values in an
+ operation and using them as parameters while invoking the linked operation.
+ """
+
+ operationRef: Optional[str] = None
+ """
+ A relative or absolute URI reference to an OAS operation.
+ This field is mutually exclusive of the `operationId` field,
+ and MUST point to an [Operation Object](#operationObject).
+ Relative `operationRef` values MAY be used to locate an existing
+ [Operation Object](#operationObject) in the OpenAPI definition.
+ """
+
+ operationId: Optional[str] = None
+ """
+ The name of an _existing_, resolvable OAS operation, as defined with a unique
+ `operationId`.
+
+ This field is mutually exclusive of the `operationRef` field.
+ """
+
+ parameters: Optional[Dict[str, Any]] = None
+ """
+ A map representing parameters to pass to an operation
+ as specified with `operationId` or identified via `operationRef`.
+ The key is the parameter name to be used,
+ whereas the value can be a constant or an expression to be evaluated and passed to
+ the linked operation.
+
+ The parameter name can be qualified using the [parameter location](#parameterIn)
+ `[{in}.]{name}` for operations that use the same parameter name in different
+ locations (e.g. path.id).
+ """
+
+ requestBody: Optional[Any] = None
+ """
+ A literal value or [{expression}](#runtimeExpression) to use as a request body when
+ calling the target operation.
+ """
+
+ description: Optional[str] = None
+ """
+ A description of the link.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ server: Optional[Server] = None
+ """
+ A server object to be used by the target operation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/media_type.py b/openapi_pydantic/v3/v3_0/media_type.py
new file mode 100644
index 0000000..b9f591f
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/media_type.py
@@ -0,0 +1,96 @@
+from typing import Any, Dict, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .encoding import Encoding
+from .example import Example
+from .reference import Reference
+from .schema import Schema
+
+_examples = [
+ {
+ "schema": {"$ref": "#/components/schemas/Pet"},
+ "examples": {
+ "cat": {
+ "summary": "An example of a cat",
+ "value": {
+ "name": "Fluffy",
+ "petType": "Cat",
+ "color": "White",
+ "gender": "male",
+ "breed": "Persian",
+ },
+ },
+ "dog": {
+ "summary": "An example of a dog with a cat's name",
+ "value": {
+ "name": "Puma",
+ "petType": "Dog",
+ "color": "Black",
+ "gender": "Female",
+ "breed": "Mixed",
+ },
+ },
+ },
+ }
+]
+
+
+class MediaType(BaseModel):
+ """Each Media Type Object provides schema and examples for the media type
+ identified by its key."""
+
+ media_type_schema: Optional[Union[Reference, Schema]] = Field(
+ default=None, alias="schema"
+ )
+ """
+ The schema defining the content of the request, response, or parameter.
+ """
+
+ example: Optional[Any] = None
+ """
+ Example of the media type.
+
+ The example object SHOULD be in the correct format as specified by the media type.
+
+ The `example` field is mutually exclusive of the `examples` field.
+
+ Furthermore, if referencing a `schema` which contains an example,
+ the `example` value SHALL _override_ the example provided by the schema.
+ """
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """
+ Examples of the media type.
+
+ Each example object SHOULD match the media type and specified schema if present.
+
+ The `examples` field is mutually exclusive of the `example` field.
+
+ Furthermore, if referencing a `schema` which contains an example,
+ the `examples` value SHALL _override_ the example provided by the schema.
+ """
+
+ encoding: Optional[Dict[str, Encoding]] = None
+ """
+ A map between a property name and its encoding information.
+ The key, being the property name, MUST exist in the schema as a property.
+ The encoding object SHALL only apply to `requestBody` objects
+ when the media type is `multipart` or `application/x-www-form-urlencoded`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/oauth_flow.py b/openapi_pydantic/v3/v3_0/oauth_flow.py
new file mode 100644
index 0000000..658d62b
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/oauth_flow.py
@@ -0,0 +1,76 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+ {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "tokenUrl": "https://example.com/api/oauth/token",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+ {
+ "authorizationUrl": "/api/oauth/dialog",
+ "tokenUrl": "/api/oauth/token",
+ "refreshUrl": "/api/oauth/token",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+]
+
+
+class OAuthFlow(BaseModel):
+ """
+ Configuration details for a supported OAuth Flow
+ """
+
+ authorizationUrl: Optional[str] = None
+ """
+ **REQUIRED** for `oauth2 ("implicit", "authorizationCode")`.
+ The authorization URL to be used for this flow.
+ This MUST be in the form of a URL.
+ """
+
+ tokenUrl: Optional[str] = None
+ """
+ **REQUIRED** for `oauth2 ("password", "clientCredentials", "authorizationCode")`.
+ The token URL to be used for this flow.
+ This MUST be in the form of a URL.
+ """
+
+ refreshUrl: Optional[str] = None
+ """
+ The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL.
+ """
+
+ scopes: Dict[str, str]
+ """
+ **REQUIRED**. The available scopes for the OAuth2 security scheme.
+ A map between the scope name and a short description for it.
+ The map MAY be empty.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/oauth_flows.py b/openapi_pydantic/v3/v3_0/oauth_flows.py
new file mode 100644
index 0000000..c8a8e5a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/oauth_flows.py
@@ -0,0 +1,47 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .oauth_flow import OAuthFlow
+
+
+class OAuthFlows(BaseModel):
+ """
+ Allows configuration of the supported OAuth Flows.
+ """
+
+ implicit: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Implicit flow
+ """
+
+ password: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Resource Owner Password flow
+ """
+
+ clientCredentials: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Client Credentials flow.
+
+ Previously called `application` in OpenAPI 2.0.
+ """
+
+ authorizationCode: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Authorization Code flow.
+
+ Previously called `accessCode` in OpenAPI 2.0.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_0/open_api.py b/openapi_pydantic/v3/v3_0/open_api.py
new file mode 100644
index 0000000..b436ffa
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/open_api.py
@@ -0,0 +1,84 @@
+from typing import List, Literal, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .components import Components
+from .external_documentation import ExternalDocumentation
+from .info import Info
+from .paths import Paths
+from .security_requirement import SecurityRequirement
+from .server import Server
+from .tag import Tag
+
+
+class OpenAPI(BaseModel):
+ """This is the root document object of the OpenAPI document."""
+
+ openapi: Literal["3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0"] = "3.0.4"
+ """
+ **REQUIRED**. This string MUST be the [semantic version number](https://semver.org/spec/v2.0.0.html)
+ of the [OpenAPI Specification version](#versions) that the OpenAPI document uses.
+ The `openapi` field SHOULD be used by tooling specifications and clients to
+ interpret the OpenAPI document. This is *not* related to the API
+ [`info.version`](#infoVersion) string.
+ """
+
+ info: Info
+ """
+ **REQUIRED**. Provides metadata about the API. The metadata MAY be used by tooling
+ as required.
+ """
+
+ servers: List[Server] = [Server(url="/")]
+ """
+ An array of Server Objects, which provide connectivity information to a target
+ server. If the `servers` property is not provided, or is an empty array,
+ the default value would be a [Server Object](#serverObject) with a
+ [url](#serverUrl) value of `/`.
+ """
+
+ paths: Paths
+ """
+ **REQUIRED**. The available paths and operations for the API.
+ """
+
+ components: Optional[Components] = None
+ """
+ An element to hold various schemas for the specification.
+ """
+
+ security: Optional[List[SecurityRequirement]] = None
+ """
+ A declaration of which security mechanisms can be used across the API.
+ The list of values includes alternative security requirement objects that can be
+ used. Only one of the security requirement objects need to be satisfied to
+ authorize a request. Individual operations can override this definition.
+ To make security optional, an empty security requirement (`{}`) can be included in
+ the array.
+ """
+
+ tags: Optional[List[Tag]] = None
+ """
+ A list of tags used by the specification with additional metadata.
+ The order of the tags can be used to reflect on their order by the parsing tools.
+ Not all tags that are used by the [Operation Object](#operationObject) must be
+ declared. The tags that are not declared MAY be organized randomly or based on the
+ tools' logic. Each tag name in the list MUST be unique.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_0/operation.py b/openapi_pydantic/v3/v3_0/operation.py
new file mode 100644
index 0000000..d8f50cc
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/operation.py
@@ -0,0 +1,173 @@
+from typing import Dict, List, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .callback import Callback
+from .external_documentation import ExternalDocumentation
+from .parameter import Parameter
+from .reference import Reference
+from .request_body import RequestBody
+from .responses import Responses
+from .security_requirement import SecurityRequirement
+from .server import Server
+
+_examples = [
+ {
+ "tags": ["pet"],
+ "summary": "Updates a pet in the store with form data",
+ "operationId": "updatePetWithForm",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet that needs to be updated",
+ "required": True,
+ "schema": {"type": "string"},
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "Updated name of the pet",
+ "type": "string",
+ },
+ "status": {
+ "description": "Updated status of the pet",
+ "type": "string",
+ },
+ },
+ "required": ["status"],
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Pet updated.",
+ "content": {"application/json": {}, "application/xml": {}},
+ },
+ "405": {
+ "description": "Method Not Allowed",
+ "content": {"application/json": {}, "application/xml": {}},
+ },
+ },
+ "security": [{"petstore_auth": ["write:pets", "read:pets"]}],
+ }
+]
+
+
+class Operation(BaseModel):
+ """Describes a single API operation on a path."""
+
+ tags: Optional[List[str]] = None
+ """
+ A list of tags for API documentation control.
+ Tags can be used for logical grouping of operations by resources or any other
+ qualifier.
+ """
+
+ summary: Optional[str] = None
+ """
+ A short summary of what the operation does.
+ """
+
+ description: Optional[str] = None
+ """
+ A verbose explanation of the operation behavior.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this operation.
+ """
+
+ operationId: Optional[str] = None
+ """
+ Unique string used to identify the operation.
+ The id MUST be unique among all operations described in the API.
+ The operationId value is **case-sensitive**.
+ Tools and libraries MAY use the operationId to uniquely identify an operation,
+ therefore, it is RECOMMENDED to follow common programming naming conventions.
+ """
+
+ parameters: Optional[List[Union[Parameter, Reference]]] = None
+ """
+ A list of parameters that are applicable for this operation.
+ If a parameter is already defined at the [Path Item](#pathItemParameters),
+ the new definition will override it but can never remove it.
+ The list MUST NOT include duplicated parameters.
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn). The list can use the [Reference Object](#referenceObject)
+ to link to parameters that are defined at the
+ [OpenAPI Object's components/parameters](#componentsParameters).
+ """
+
+ requestBody: Optional[Union[RequestBody, Reference]] = None
+ """
+ The request body applicable for this operation.
+
+ The `requestBody` is only supported in HTTP methods where the HTTP 1.1 specification
+ [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has explicitly defined
+ semantics for request bodies. In other cases where the HTTP spec is vague,
+ `requestBody` SHALL be ignored by consumers.
+ """
+
+ responses: Responses
+ """
+ **REQUIRED**. The list of possible responses as they are returned from executing
+ this operation.
+ """
+
+ callbacks: Optional[Dict[str, Callback]] = None
+ """
+ A map of possible out-of band callbacks related to the parent operation.
+ The key is a unique identifier for the Callback Object.
+ Each value in the map is a [Callback Object](#callbackObject)
+ that describes a request that may be initiated by the API provider and the expected
+ responses.
+ """
+
+ deprecated: bool = False
+ """
+ Declares this operation to be deprecated.
+ Consumers SHOULD refrain from usage of the declared operation.
+ Default value is `false`.
+ """
+
+ security: Optional[List[SecurityRequirement]] = None
+ """
+ A declaration of which security mechanisms can be used for this operation.
+ The list of values includes alternative security requirement objects that can be
+ used. Only one of the security requirement objects need to be satisfied to
+ authorize a request. To make security optional, an empty security requirement
+ (`{}`) can be included in the array. This definition overrides any declared
+ top-level [`security`](#oasSecurity). To remove a top-level security declaration,
+ an empty array can be used.
+ """
+
+ servers: Optional[List[Server]] = None
+ """
+ An alternative `server` array to service this operation.
+ If an alternative `server` object is specified at the Path Item Object or Root
+ level, it will be overridden by this value.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/parameter.py b/openapi_pydantic/v3/v3_0/parameter.py
new file mode 100644
index 0000000..80498ba
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/parameter.py
@@ -0,0 +1,234 @@
+import enum
+from typing import Any, Dict, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .example import Example
+from .media_type import MediaType
+from .reference import Reference
+from .schema import Schema
+
+_examples = [
+ {
+ "name": "token",
+ "in": "header",
+ "description": "token to be passed as a header",
+ "required": True,
+ "schema": {
+ "type": "array",
+ "items": {"type": "integer", "format": "int64"},
+ },
+ "style": "simple",
+ },
+ {
+ "name": "username",
+ "in": "path",
+ "description": "username to fetch",
+ "required": True,
+ "schema": {"type": "string"},
+ },
+ {
+ "name": "id",
+ "in": "query",
+ "description": "ID of the object to fetch",
+ "required": False,
+ "schema": {"type": "array", "items": {"type": "string"}},
+ "style": "form",
+ "explode": True,
+ },
+ {
+ "in": "query",
+ "name": "freeForm",
+ "schema": {
+ "type": "object",
+ "additionalProperties": {"type": "integer"},
+ },
+ "style": "form",
+ },
+ {
+ "in": "query",
+ "name": "coordinates",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["lat", "long"],
+ "properties": {
+ "lat": {"type": "number"},
+ "long": {"type": "number"},
+ },
+ }
+ }
+ },
+ },
+]
+
+
+class ParameterLocation(str, enum.Enum):
+ """The location of a given parameter."""
+
+ QUERY = "query"
+ HEADER = "header"
+ PATH = "path"
+ COOKIE = "cookie"
+
+
+class ParameterBase(BaseModel):
+ """
+ Base class for Parameter and Header.
+
+ (Header is like Parameter, but has no `name` or `in` fields.)
+ """
+
+ description: Optional[str] = None
+ """
+ A brief description of the parameter.
+ This could contain examples of use.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ required: bool = False
+ """
+ Determines whether this parameter is mandatory.
+ If the [parameter location](#parameterIn) is `"path"`, this property is
+ **REQUIRED** and its value MUST be `true`.
+ Otherwise, the property MAY be included and its default value is `false`.
+ """
+
+ deprecated: bool = False
+ """
+ Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
+ Default value is `false`.
+ """
+
+ style: Optional[str] = None
+ """
+ Describes how the parameter value will be serialized depending on the type of the
+ parameter value. Default values (based on value of `in`):
+
+ - for `query` - `form`;
+ - for `path` - `simple`;
+ - for `header` - `simple`;
+ - for `cookie` - `form`.
+ """
+
+ 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.
+ For other types of parameters this property has no effect.
+ When [`style`](#parameterStyle) is `form`, the default value is `true`.
+ For all other styles, the default value is `false`.
+ """
+
+ param_schema: Optional[Union[Reference, Schema]] = Field(
+ default=None, alias="schema"
+ )
+ """
+ The schema defining the type used for the parameter.
+ """
+
+ example: Optional[Any] = None
+ """
+ Example of the parameter's potential value.
+ The example SHOULD match the specified schema and encoding properties if present.
+ The `example` field is mutually exclusive of the `examples` field.
+ Furthermore, if referencing a `schema` that contains an example,
+ the `example` value SHALL _override_ the example provided by the schema.
+ To represent examples of media types that cannot naturally be represented in JSON
+ or YAML, a string value can contain the example with escaping where necessary.
+ """
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """
+ Examples of the parameter's potential value.
+ Each example SHOULD contain a value in the correct format as specified in the
+ parameter encoding. The `examples` field is mutually exclusive of the `example`
+ field. Furthermore, if referencing a `schema` that contains an example,
+ the `examples` value SHALL _override_ the example provided by the schema.
+ """
+
+ """
+ For more complex scenarios, the [`content`](#parameterContent) property
+ can define the media type and schema of the parameter.
+ A parameter MUST contain either a `schema` property, or a `content` property, but
+ not both. When `example` or `examples` are provided in conjunction with the
+ `schema` object, the example MUST follow the prescribed serialization strategy for
+ the parameter.
+ """
+
+ content: Optional[Dict[str, MediaType]] = None
+ """
+ A map containing the representations for the parameter.
+ The key is the media type and the value describes it.
+ The map MUST only contain one entry.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
+
+
+class Parameter(ParameterBase):
+ """
+ Describes a single operation parameter.
+
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn).
+ """
+
+ """Fixed Fields"""
+
+ name: str
+ """
+ **REQUIRED**. The name of the parameter.
+ Parameter names are *case sensitive*.
+
+ - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a
+ template expression occurring within the [path](#pathsPath) field in the
+ [Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further
+ information.
+ - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`,
+ `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored.
+ - For all other cases, the `name` corresponds to the parameter name used by the
+ [`in`](#parameterIn) property.
+ """
+
+ param_in: ParameterLocation = Field(alias="in")
+ """
+ **REQUIRED**. The location of the parameter. Possible values are `"query"`,
+ `"header"`, `"path"` or `"cookie"`.
+ """
+
+ allowEmptyValue: bool = False
+ """
+ Sets the ability to pass empty-valued parameters.
+ This is valid only for `query` parameters and allows sending a parameter with an
+ empty value. Default value is `false`.
+ If [`style`](#parameterStyle) is used, and if behavior is `n/a` (cannot be
+ serialized), the value of `allowEmptyValue` SHALL be ignored.
+ Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later
+ revision.
+ """
+
+ allowReserved: bool = False
+ """
+ Determines whether the parameter value SHOULD allow reserved characters,
+ as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
+ `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
+ This property only applies to parameters with an `in` value of `query`.
+ The default value is `false`.
+ """
diff --git a/openapi_pydantic/v3/v3_0/path_item.py b/openapi_pydantic/v3/v3_0/path_item.py
new file mode 100644
index 0000000..2344e9c
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/path_item.py
@@ -0,0 +1,152 @@
+from typing import List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .operation import Operation
+from .parameter import Parameter
+from .reference import Reference
+from .server import Server
+
+_examples = [
+ {
+ "get": {
+ "description": "Returns pets based on ID",
+ "summary": "Find pets by ID",
+ "operationId": "getPetsById",
+ "responses": {
+ "200": {
+ "description": "pet response",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Pet"},
+ }
+ }
+ },
+ },
+ "default": {
+ "description": "error payload",
+ "content": {
+ "text/html": {
+ "schema": {"$ref": "#/components/schemas/ErrorModel"}
+ }
+ },
+ },
+ },
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "ID of pet to use",
+ "required": True,
+ "schema": {"type": "array", "items": {"type": "string"}},
+ "style": "simple",
+ }
+ ],
+ }
+]
+
+
+class PathItem(BaseModel):
+ """
+ Describes the operations available on a single path.
+ A Path Item MAY be empty, due to [ACL constraints](#securityFiltering).
+ The path itself is still exposed to the documentation viewer
+ but they will not know which operations and parameters are available.
+ """
+
+ ref: Optional[str] = Field(default=None, alias="$ref")
+ """
+ Allows for an external definition of this path item.
+ The referenced structure MUST be in the format of a
+ [Path Item Object](#pathItemObject).
+
+ In case a Path Item Object field appears both in the defined object and the
+ referenced object, the behavior is undefined.
+ """
+
+ summary: Optional[str] = None
+ """
+ An optional, string summary, intended to apply to all operations in this path.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional, string description, intended to apply to all operations in this path.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ get: Optional[Operation] = None
+ """
+ A definition of a GET operation on this path.
+ """
+
+ put: Optional[Operation] = None
+ """
+ A definition of a PUT operation on this path.
+ """
+
+ post: Optional[Operation] = None
+ """
+ A definition of a POST operation on this path.
+ """
+
+ delete: Optional[Operation] = None
+ """
+ A definition of a DELETE operation on this path.
+ """
+
+ options: Optional[Operation] = None
+ """
+ A definition of a OPTIONS operation on this path.
+ """
+
+ head: Optional[Operation] = None
+ """
+ A definition of a HEAD operation on this path.
+ """
+
+ patch: Optional[Operation] = None
+ """
+ A definition of a PATCH operation on this path.
+ """
+
+ trace: Optional[Operation] = None
+ """
+ A definition of a TRACE operation on this path.
+ """
+
+ servers: Optional[List[Server]] = None
+ """
+ An alternative `server` array to service all operations in this path.
+ """
+
+ parameters: Optional[List[Union[Parameter, Reference]]] = None
+ """
+ A list of parameters that are applicable for all the operations described under
+ this path. These parameters can be overridden at the operation level, but cannot be
+ removed there. The list MUST NOT include duplicated parameters.
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn). The list can use the [Reference Object](#referenceObject)
+ to link to parameters that are defined at the
+ [OpenAPI Object's components/parameters](#componentsParameters).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/paths.py b/openapi_pydantic/v3/v3_0/paths.py
new file mode 100644
index 0000000..db258e6
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/paths.py
@@ -0,0 +1,27 @@
+from typing import Dict
+
+from .path_item import PathItem
+
+Paths = Dict[str, PathItem]
+"""
+Holds the relative paths to the individual endpoints and their operations.
+The path is appended to the URL from the [`Server Object`](#serverObject) in order to
+construct the full URL.
+
+The Paths MAY be empty, due to [ACL constraints](#securityFiltering).
+"""
+
+"""Patterned Fields"""
+
+# "/{path}" : PathItem
+"""
+A relative path to an individual endpoint.
+The field name MUST begin with a forward slash (`/`).
+The path is **appended** (no relative URL resolution) to the expanded URL
+from the [`Server Object`](#serverObject)'s `url` field in order to construct the full
+URL. [Path templating](#pathTemplating) is allowed.
+When matching URLs, concrete (non-templated) paths would be matched before their
+templated counterparts. Templated paths with the same hierarchy but different templated
+names MUST NOT exist as they are identical. In case of ambiguous matching, it's up to
+the tooling to decide which one to use.
+"""
diff --git a/openapi_pydantic/v3/v3_0/reference.py b/openapi_pydantic/v3/v3_0/reference.py
new file mode 100644
index 0000000..29b2206
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/reference.py
@@ -0,0 +1,38 @@
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {"$ref": "#/components/schemas/Pet"},
+ {"$ref": "Pet.json"},
+ {"$ref": "definitions.json#/Pet"},
+]
+
+
+class Reference(BaseModel):
+ """
+ A simple object to allow referencing other components in the specification.
+
+ The Reference Object is defined by [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03)
+ and follows the same structure, behavior and rules.
+
+ For this specification, reference resolution is accomplished as defined by the JSON
+ Reference specification and not by the JSON Schema specification.
+ """
+
+ ref: str = Field(alias="$ref")
+ """**REQUIRED**. The reference string."""
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/request_body.py b/openapi_pydantic/v3/v3_0/request_body.py
new file mode 100644
index 0000000..1741a5a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/request_body.py
@@ -0,0 +1,95 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .media_type import MediaType
+
+_examples = [
+ {
+ "description": "user to add to the system",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"},
+ "examples": {
+ "user": {
+ "summary": "User Example",
+ "externalValue": "http://foo.bar/examples/user-example.json",
+ }
+ },
+ },
+ "application/xml": {
+ "schema": {"$ref": "#/components/schemas/User"},
+ "examples": {
+ "user": {
+ "summary": "User example in XML",
+ "externalValue": "http://foo.bar/examples/user-example.xml",
+ }
+ },
+ },
+ "text/plain": {
+ "examples": {
+ "user": {
+ "summary": "User example in Plain text",
+ "externalValue": "http://foo.bar/examples/user-example.txt",
+ }
+ }
+ },
+ "*/*": {
+ "examples": {
+ "user": {
+ "summary": "User example in other format",
+ "externalValue": "http://foo.bar/examples/user-example.whatever",
+ }
+ }
+ },
+ },
+ },
+ {
+ "description": "user to add to the system",
+ "content": {
+ "text/plain": {"schema": {"type": "array", "items": {"type": "string"}}}
+ },
+ },
+]
+
+
+class RequestBody(BaseModel):
+ """Describes a single request body."""
+
+ description: Optional[str] = None
+ """
+ A brief description of the request body.
+ This could contain examples of use.
+
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ content: Dict[str, MediaType]
+ """
+ **REQUIRED**. The content of the request body.
+ The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D)
+ and the value describes it.
+
+ For requests that match multiple keys, only the most specific key is applicable.
+ e.g. text/plain overrides text/*
+ """
+
+ required: bool = False
+ """
+ Determines if the request body is required in the request. Defaults to `false`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/response.py b/openapi_pydantic/v3/v3_0/response.py
new file mode 100644
index 0000000..107962d
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/response.py
@@ -0,0 +1,101 @@
+from typing import Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .header import Header
+from .link import Link
+from .media_type import MediaType
+from .reference import Reference
+
+_examples = [
+ {
+ "description": "A complex object array response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/VeryComplexType"},
+ }
+ }
+ },
+ },
+ {
+ "description": "A simple string response",
+ "content": {"text/plain": {"schema": {"type": "string"}}},
+ },
+ {
+ "description": "A simple string response",
+ "content": {"text/plain": {"schema": {"type": "string", "example": "whoa!"}}},
+ "headers": {
+ "X-Rate-Limit-Limit": {
+ "description": ("The number of allowed requests in the current period"),
+ "schema": {"type": "integer"},
+ },
+ "X-Rate-Limit-Remaining": {
+ "description": (
+ "The number of remaining requests in the current period"
+ ),
+ "schema": {"type": "integer"},
+ },
+ "X-Rate-Limit-Reset": {
+ "description": ("The number of seconds left in the current period"),
+ "schema": {"type": "integer"},
+ },
+ },
+ },
+ {"description": "object created"},
+]
+
+
+class Response(BaseModel):
+ """
+ Describes a single response from an API Operation, including design-time,
+ static `links` to operations based on the response.
+ """
+
+ description: str
+ """
+ **REQUIRED**. A short description of the response.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ headers: Optional[Dict[str, Union[Header, Reference]]] = None
+ """
+ Maps a header name to its definition.
+ [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) states header names are case
+ insensitive. If a response header is defined with the name `"Content-Type"`, it
+ SHALL be ignored.
+ """
+
+ content: Optional[Dict[str, MediaType]] = None
+ """
+ A map containing descriptions of potential response payloads.
+ The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D)
+ and the value describes it.
+
+ For responses that match multiple keys, only the most specific key is applicable.
+ e.g. text/plain overrides text/*
+ """
+
+ links: Optional[Dict[str, Union[Link, Reference]]] = None
+ """
+ A map of operations links that can be followed from the response.
+ The key of the map is a short name for the link,
+ following the naming constraints of the names for
+ [Component Objects](#componentsObject).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/responses.py b/openapi_pydantic/v3/v3_0/responses.py
new file mode 100644
index 0000000..4bd45e3
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/responses.py
@@ -0,0 +1,49 @@
+from typing import Dict, Union
+
+from .reference import Reference
+from .response import Response
+
+Responses = Dict[str, Union[Response, Reference]]
+"""
+A container for the expected responses of an operation.
+The container maps a HTTP response code to the expected response.
+
+The documentation is not necessarily expected to cover all possible HTTP response codes
+because they may not be known in advance.
+However, documentation is expected to cover a successful operation response and any
+known errors.
+
+The `default` MAY be used as a default response object for all HTTP codes
+that are not covered individually by the specification.
+
+The `Responses Object` MUST contain at least one response code, and it
+SHOULD be the response for a successful operation call.
+"""
+
+"""Fixed Fields"""
+
+# default: Optional[Union[Response, Reference]]
+"""
+The documentation of responses other than the ones declared for specific HTTP response
+codes. Use this field to cover undeclared responses.
+A [Reference Object](#referenceObject) can link to a response
+that the [OpenAPI Object's components/responses](#componentsResponses) section defines.
+"""
+
+"""Patterned Fields"""
+# {httpStatusCode]: Optional[Union[Response, Reference]]
+"""
+Any [HTTP status code](#httpCodes) can be used as the property name,
+but only one property per code, to describe the expected response for that HTTP status
+code.
+
+A [Reference Object](#referenceObject) can link to a response
+that is defined in the [OpenAPI Object's components/responses](#componentsResponses)
+section. This field MUST be enclosed in quotation marks (for example, "200") for
+compatibility between JSON and YAML. To define a range of response codes, this field
+MAY contain the uppercase wildcard character `X`. For example, `2XX` represents all
+response codes between `[200-299]`. Only the following range definitions are allowed:
+`1XX`, `2XX`, `3XX`, `4XX`, and `5XX`.
+If a response is defined using an explicit code,
+the explicit code definition takes precedence over the range definition for that code.
+"""
diff --git a/openapi_pydantic/v3/v3_0/schema.py b/openapi_pydantic/v3/v3_0/schema.py
new file mode 100644
index 0000000..e3f04ce
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/schema.py
@@ -0,0 +1,614 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra, min_length_arg
+
+from .datatype import DataType
+from .discriminator import Discriminator
+from .external_documentation import ExternalDocumentation
+from .reference import Reference
+from .xml import XML
+
+_examples = [
+ {"type": "string", "format": "email"},
+ {
+ "type": "object",
+ "required": ["name"],
+ "properties": {
+ "name": {"type": "string"},
+ "address": {"$ref": "#/components/schemas/Address"},
+ "age": {"type": "integer", "format": "int32", "minimum": 0},
+ },
+ },
+ {"type": "object", "additionalProperties": {"type": "string"}},
+ {
+ "type": "object",
+ "additionalProperties": {"$ref": "#/components/schemas/ComplexModel"},
+ },
+ {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ "required": ["name"],
+ "example": {"name": "Puma", "id": 1},
+ },
+ {
+ "type": "object",
+ "required": ["message", "code"],
+ "properties": {
+ "message": {"type": "string"},
+ "code": {"type": "integer", "minimum": 100, "maximum": 600},
+ },
+ },
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/ErrorModel"},
+ {
+ "type": "object",
+ "required": ["rootCause"],
+ "properties": {"rootCause": {"type": "string"}},
+ },
+ ]
+ },
+ {
+ "type": "object",
+ "discriminator": {"propertyName": "petType"},
+ "properties": {
+ "name": {"type": "string"},
+ "petType": {"type": "string"},
+ },
+ "required": ["name", "petType"],
+ },
+ {
+ "description": "A representation of a cat. "
+ "Note that `Cat` will be used as the discriminator value.",
+ "allOf": [
+ {"$ref": "#/components/schemas/Pet"},
+ {
+ "type": "object",
+ "properties": {
+ "huntingSkill": {
+ "type": "string",
+ "description": "The measured skill for hunting",
+ "default": "lazy",
+ "enum": [
+ "clueless",
+ "lazy",
+ "adventurous",
+ "aggressive",
+ ],
+ }
+ },
+ "required": ["huntingSkill"],
+ },
+ ],
+ },
+ {
+ "description": "A representation of a dog. "
+ "Note that `Dog` will be used as the discriminator value.",
+ "allOf": [
+ {"$ref": "#/components/schemas/Pet"},
+ {
+ "type": "object",
+ "properties": {
+ "packSize": {
+ "type": "integer",
+ "format": "int32",
+ "description": ("the size of the pack the dog is from"),
+ "default": 0,
+ "minimum": 0,
+ }
+ },
+ "required": ["packSize"],
+ },
+ ],
+ },
+]
+
+
+class Schema(BaseModel):
+ """
+ The Schema Object allows the definition of input and output data types.
+ These types can be objects, but also primitives and arrays.
+ This object is an extended subset of the [JSON Schema Specification Wright Draft 00](https://json-schema.org/).
+
+ For more information about the properties,
+ see [JSON Schema Core](https://tools.ietf.org/html/draft-wright-json-schema-00)
+ and [JSON Schema Validation](https://tools.ietf.org/html/draft-wright-json-schema-validation-00).
+ Unless stated otherwise, the property definitions follow the JSON Schema.
+ """
+
+ """
+ The following properties are taken directly from the JSON Schema definition and
+ follow the same specifications:
+ """
+
+ title: Optional[str] = None
+ """
+ The value of "title" MUST be a string.
+
+ The title can be used to decorate a user interface with
+ information about the data produced by this user interface.
+ The title will preferrably be short.
+ """
+
+ multipleOf: Optional[float] = Field(default=None, gt=0.0)
+ """
+ The value of "multipleOf" MUST be a number, strictly greater than 0.
+
+ A numeric instance is only valid if division by this keyword's value
+ results in an integer.
+ """
+
+ maximum: Optional[float] = None
+ """
+ The value of "maximum" MUST be a number, representing an upper limit
+ for a numeric instance.
+
+ If the instance is a number, then this keyword validates if
+ "exclusiveMaximum" is true and instance is less than the provided
+ value, or else if the instance is less than or exactly equal to the
+ provided value.
+ """
+
+ exclusiveMaximum: Optional[bool] = None
+ """
+ The value of "exclusiveMaximum" MUST be a boolean, representing
+ whether the limit in "maximum" is exclusive or not. An undefined
+ value is the same as false.
+
+ If "exclusiveMaximum" is true, then a numeric instance SHOULD NOT be
+ equal to the value specified in "maximum". If "exclusiveMaximum" is
+ false (or not specified), then a numeric instance MAY be equal to the
+ value of "maximum".
+ """
+
+ minimum: Optional[float] = None
+ """
+ The value of "minimum" MUST be a number, representing a lower limit
+ for a numeric instance.
+
+ If the instance is a number, then this keyword validates if
+ "exclusiveMinimum" is true and instance is greater than the provided
+ value, or else if the instance is greater than or exactly equal to
+ the provided value.
+ """
+
+ exclusiveMinimum: Optional[bool] = None
+ """
+ The value of "exclusiveMinimum" MUST be a boolean, representing
+ whether the limit in "minimum" is exclusive or not. An undefined
+ value is the same as false.
+
+ If "exclusiveMinimum" is true, then a numeric instance SHOULD NOT be
+ equal to the value specified in "minimum". If "exclusiveMinimum" is
+ false (or not specified), then a numeric instance MAY be equal to the
+ value of "minimum".
+ """
+
+ maxLength: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ A string instance is valid against this keyword if its length is less
+ than, or equal to, the value of this keyword.
+
+ The length of a string instance is defined as the number of its
+ characters as defined by RFC 7159 [RFC7159].
+ """
+
+ minLength: Optional[int] = Field(default=None, ge=0)
+ """
+ A string instance is valid against this keyword if its length is
+ greater than, or equal to, the value of this keyword.
+
+ The length of a string instance is defined as the number of its
+ characters as defined by RFC 7159 [RFC7159].
+
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ "minLength", if absent, may be considered as being present with
+ integer value 0.
+ """
+
+ pattern: Optional[str] = None
+ """
+ The value of this keyword MUST be a string. This string SHOULD be a
+ valid regular expression, according to the ECMA 262 regular
+ expression dialect.
+
+ A string instance is considered valid if the regular expression
+ matches the instance successfully. Recall: regular expressions are
+ not implicitly anchored.
+ """
+
+ maxItems: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ An array instance is valid against "maxItems" if its size is less
+ than, or equal to, the value of this keyword.
+ """
+
+ minItems: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ An array instance is valid against "minItems" if its size is greater
+ than, or equal to, the value of this keyword.
+
+ If this keyword is not present, it may be considered present with a
+ value of 0.
+ """
+
+ uniqueItems: Optional[bool] = None
+ """
+ The value of this keyword MUST be a boolean.
+
+ If this keyword has boolean value false, the instance validates
+ successfully. If it has boolean value true, the instance validates
+ successfully if all of its elements are unique.
+
+ If not present, this keyword may be considered present with boolean
+ value false.
+ """
+
+ maxProperties: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ An object instance is valid against "maxProperties" if its number of
+ properties is less than, or equal to, the value of this keyword.
+ """
+
+ minProperties: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be an integer. This integer MUST be
+ greater than, or equal to, 0.
+
+ An object instance is valid against "minProperties" if its number of
+ properties is greater than, or equal to, the value of this keyword.
+
+ If this keyword is not present, it may be considered present with a
+ value of 0.
+ """
+
+ required: Optional[List[str]] = Field(default=None, **min_length_arg(1))
+ """
+ The value of this keyword MUST be an array. This array MUST have at
+ least one element. Elements of this array MUST be strings, and MUST
+ be unique.
+
+ An object instance is valid against this keyword if its property set
+ contains all elements in this keyword's array value.
+ """
+
+ enum: Optional[List[Any]] = Field(default=None, **min_length_arg(1))
+ """
+ The value of this keyword MUST be an array. This array SHOULD have
+ at least one element. Elements in the array SHOULD be unique.
+
+ Elements in the array MAY be of any type, including null.
+
+ An instance validates successfully against this keyword if its value
+ is equal to one of the elements in this keyword's array value.
+ """
+
+ """
+ The following properties are taken from the JSON Schema definition
+ but their definitions were adjusted to the OpenAPI Specification.
+ """
+
+ type: Optional[DataType] = None
+ """
+ **From OpenAPI spec:
+ Value MUST be a string. Multiple types via an array are not supported.**
+
+ From JSON Schema:
+ The value of this keyword MUST be either a string or an array. If it
+ is an array, elements of the array MUST be strings and MUST be
+ unique.
+
+ String values MUST be one of the seven primitive types defined by the
+ core specification.
+
+ An instance matches successfully if its primitive type is one of the
+ types defined by keyword. Recall: "number" includes "integer".
+ """
+
+ allOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ **From OpenAPI spec:
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema.**
+
+ From JSON Schema:
+ This keyword's value MUST be an array. This array MUST have at least
+ one element.
+
+ Elements of the array MUST be objects. Each object MUST be a valid
+ JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against all schemas defined by this keyword's
+ value.
+ """
+
+ oneOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ **From OpenAPI spec:
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema.**
+
+ From JSON Schema:
+ This keyword's value MUST be an array. This array MUST have at least
+ one element.
+
+ Elements of the array MUST be objects. Each object MUST be a valid
+ JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against exactly one schema defined by this
+ keyword's value.
+ """
+
+ anyOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ **From OpenAPI spec:
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema.**
+
+ From JSON Schema:
+ This keyword's value MUST be an array. This array MUST have at least
+ one element.
+
+ Elements of the array MUST be objects. Each object MUST be a valid
+ JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against at least one schema defined by this
+ keyword's value.
+ """
+
+ schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not")
+ """
+ **From OpenAPI spec:
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema.**
+
+ From JSON Schema:
+ This keyword's value MUST be an object. This object MUST be a valid
+ JSON Schema.
+
+ An instance is valid against this keyword if it fails to validate
+ successfully against the schema defined by this keyword.
+ """
+
+ items: Optional[Union[Reference, "Schema"]] = None
+ """
+ **From OpenAPI spec:
+ Value MUST be an object and not an array.
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema. `items` MUST be present if the `type` is `array`.**
+
+ From JSON Schema:
+ The value of "items" MUST be either a schema or array of schemas.
+
+ Successful validation of an array instance with regards to these two
+ keywords is determined as follows:
+
+ - if "items" is not present, or its value is an object, validation
+ of the instance always succeeds, regardless of the value of
+ "additionalItems";
+ - if the value of "additionalItems" is boolean value true or an
+ object, validation of the instance always succeeds;
+ - if the value of "additionalItems" is boolean value false and the
+ value of "items" is an array, the instance is valid if its size is
+ less than, or equal to, the size of "items".
+ """
+
+ properties: Optional[Dict[str, Union[Reference, "Schema"]]] = None
+ """
+ **From OpenAPI spec:
+ Property definitions MUST be a [Schema Object](#schemaObject)
+ and not a standard JSON Schema (inline or referenced).**
+
+ From JSON Schema:
+ The value of "properties" MUST be an object. Each value of this
+ object MUST be an object, and each object MUST be a valid JSON
+ Schema.
+
+ If absent, it can be considered the same as an empty object.
+ """
+
+ additionalProperties: Optional[Union[bool, Reference, "Schema"]] = None
+ """
+ **From OpenAPI spec:
+ Value can be boolean or object.
+ Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a
+ standard JSON Schema.
+ Consistent with JSON Schema, `additionalProperties` defaults to `true`.**
+
+ From JSON Schema:
+ The value of "additionalProperties" MUST be a boolean or a schema.
+
+ If "additionalProperties" is absent, it may be considered present
+ with an empty schema as a value.
+
+ If "additionalProperties" is true, validation always succeeds.
+
+ If "additionalProperties" is false, validation succeeds only if the
+ instance is an object and all properties on the instance were covered
+ by "properties" and/or "patternProperties".
+
+ If "additionalProperties" is an object, validate the value as a
+ schema to all of the properties that weren't validated by
+ "properties" nor "patternProperties".
+ """
+
+ description: Optional[str] = None
+ """
+ **From OpenAPI spec:
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.**
+
+ From JSON Schema:
+ The value "description" MUST be a string.
+
+ The description can be used to decorate a user interface with
+ information about the data produced by this user interface.
+ The description will provide explanation about the purpose of
+ the instance described by this schema.
+ """
+
+ schema_format: Optional[str] = Field(default=None, alias="format")
+ """
+ **From OpenAPI spec:
+ [Data Type Formats](#dataTypeFormat) for further details.
+ While relying on JSON Schema's defined formats, the OAS offers a few additional
+ predefined formats.**
+
+ From JSON Schema:
+ Structural validation alone may be insufficient to validate that an
+ instance meets all the requirements of an application. The "format"
+ keyword is defined to allow interoperable semantic validation for a
+ fixed subset of values which are accurately described by
+ authoritative resources, be they RFCs or other external
+ specifications.
+
+ The value of this keyword is called a format attribute. It MUST be a
+ string. A format attribute can generally only validate a given set
+ of instance types. If the type of the instance to validate is not in
+ this set, validation for this format attribute and instance SHOULD
+ succeed.
+ """
+
+ default: Optional[Any] = None
+ """
+ **From OpenAPI spec:
+ The default value represents what would be assumed by the consumer of the input
+ as the value of the schema if one is not provided.
+ Unlike JSON Schema, the value MUST conform to the defined type for the Schema
+ Object defined at the same level. For example, if `type` is `string`, then
+ `default` can be `"foo"` but cannot be `1`.**
+
+ From JSON Schema:
+ There are no restrictions placed on the value of this keyword.
+
+ This keyword can be used to supply a default JSON value associated
+ with a particular schema. It is RECOMMENDED that a default value be
+ valid against the associated schema.
+
+ This keyword MAY be used in root schemas, and in any subschemas.
+ """
+
+ """
+ Other than the JSON Schema subset fields, the following fields MAY be used for
+ further schema documentation:
+ """
+
+ nullable: Optional[bool] = None
+ """
+ A `true` value adds `"null"` to the allowed type specified by the `type` keyword,
+ only if `type` is explicitly defined within the same Schema Object.
+ Other Schema Object constraints retain their defined behavior,
+ and therefore may disallow the use of `null` as a value.
+ A `false` value leaves the specified or default `type` unmodified.
+ The default value is `false`.
+ """
+
+ discriminator: Optional[Discriminator] = None
+ """
+ Adds support for polymorphism.
+ The discriminator is an object name that is used to differentiate between other
+ schemas which may satisfy the payload description.
+ See [Composition and Inheritance](#schemaComposition) for more details.
+ """
+
+ readOnly: Optional[bool] = None
+ """
+ Relevant only for Schema `"properties"` definitions.
+ Declares the property as "read only".
+ This means that it MAY be sent as part of a response but SHOULD NOT be sent as part
+ of the request. If the property is marked as `readOnly` being `true` and is in the
+ `required` list, the `required` will take effect on the response only.
+ A property MUST NOT be marked as both `readOnly` and `writeOnly` being `true`.
+ Default value is `false`.
+ """
+
+ writeOnly: Optional[bool] = None
+ """
+ Relevant only for Schema `"properties"` definitions.
+ Declares the property as "write only".
+ Therefore, it MAY be sent as part of a request but SHOULD NOT be sent as part of
+ the response. If the property is marked as `writeOnly` being `true` and is in the
+ `required` list, the `required` will take effect on the request only.
+ A property MUST NOT be marked as both `readOnly` and `writeOnly` being `true`.
+ Default value is `false`.
+ """
+
+ xml: Optional[XML] = None
+ """
+ This MAY be used only on properties schemas.
+ It has no effect on root schemas.
+ Adds additional metadata to describe the XML representation of this property.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this schema.
+ """
+
+ example: Optional[Any] = None
+ """
+ A free-form property to include an example of an instance for this schema.
+ To represent examples that cannot be naturally represented in JSON or YAML,
+ a string value can be used to contain the example with escaping where necessary.
+ """
+
+ deprecated: Optional[bool] = None
+ """
+ Specifies that a schema is deprecated and SHOULD be transitioned out of usage.
+ Default value is `false`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
+
+
+if TYPE_CHECKING:
+
+ def schema_validate(
+ obj: Any,
+ *,
+ strict: Optional[bool] = None,
+ from_attributes: Optional[bool] = None,
+ context: Optional[Dict[str, Any]] = None
+ ) -> Schema: ...
+
+elif PYDANTIC_V2:
+ schema_validate = Schema.model_validate
+
+else:
+ schema_validate = Schema.parse_obj
diff --git a/openapi_pydantic/v3/v3_0/security_requirement.py b/openapi_pydantic/v3/v3_0/security_requirement.py
new file mode 100644
index 0000000..2b4e72b
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/security_requirement.py
@@ -0,0 +1,32 @@
+from typing import Dict, List
+
+SecurityRequirement = Dict[str, List[str]]
+"""
+Lists the required security schemes to execute this operation.
+The name used for each property MUST correspond to a security scheme declared in the
+[Security Schemes](#componentsSecuritySchemes) under the
+[Components Object](#componentsObject).
+
+Security Requirement Objects that contain multiple schemes require that
+all schemes MUST be satisfied for a request to be authorized.
+This enables support for scenarios where multiple query parameters or HTTP headers
+are required to convey security information.
+
+When a list of Security Requirement Objects is defined on the
+[OpenAPI Object](#oasObject) or [Operation Object](#operationObject),
+only one of the Security Requirement Objects in the list needs to be satisfied to
+authorize the request.
+"""
+
+"""Patterned Fields"""
+
+# {name}: List[str]
+"""
+Each name MUST correspond to a security scheme which is declared
+in the [Security Schemes](#componentsSecuritySchemes) under the
+[Components Object](#componentsObject).
+If the security scheme is of type `"oauth2"` or `"openIdConnect"`,
+then the value is a list of scope names required for the execution,
+and the list MAY be empty if authorization does not require a specified scope.
+For other security scheme types, the array MUST be empty.
+"""
diff --git a/openapi_pydantic/v3/v3_0/security_scheme.py b/openapi_pydantic/v3/v3_0/security_scheme.py
new file mode 100644
index 0000000..f0ab52e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/security_scheme.py
@@ -0,0 +1,112 @@
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .oauth_flows import OAuthFlows
+
+_examples = [
+ {"type": "http", "scheme": "basic"},
+ {"type": "apiKey", "name": "api_key", "in": "header"},
+ {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
+ {
+ "type": "oauth2",
+ "flows": {
+ "implicit": {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ }
+ },
+ },
+ {
+ "type": "openIdConnect",
+ "openIdConnectUrl": "https://example.com/openIdConnect",
+ },
+ {
+ "type": "openIdConnect",
+ "openIdConnectUrl": "openIdConnect",
+ }, # #5: allow relative path
+]
+
+
+class SecurityScheme(BaseModel):
+ """
+ Defines a security scheme that can be used by the operations.
+ Supported schemes are HTTP authentication,
+ an API key (either as a header, a cookie parameter or as a query parameter),
+ OAuth2's common flows (implicit, password, client credentials and authorization
+ code) as defined in [RFC6749](https://tools.ietf.org/html/rfc6749),
+ and [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06).
+ """
+
+ type: str
+ """
+ **REQUIRED**. The type of the security scheme.
+ Valid values are `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`.
+ """
+
+ description: Optional[str] = None
+ """
+ A short description for security scheme.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ name: Optional[str] = None
+ """
+ **REQUIRED** for `apiKey`. The name of the header, query or cookie parameter to be
+ used.
+ """
+
+ security_scheme_in: Optional[str] = Field(alias="in", default=None)
+ """
+ **REQUIRED** for `apiKey`. The location of the API key. Valid values are `"query"`,
+ `"header"` or `"cookie"`.
+ """
+
+ scheme: Optional[str] = None
+ """
+ **REQUIRED** for `http`. The name of the HTTP Authorization scheme to be used in the
+ [Authorization header as defined in RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1).
+
+ The values used SHOULD be registered in the
+ [IANA Authentication Scheme registry](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml).
+ """
+
+ bearerFormat: Optional[str] = None
+ """
+ A hint to the client to identify how the bearer token is formatted.
+
+ Bearer tokens are usually generated by an authorization server,
+ so this information is primarily for documentation purposes.
+ """
+
+ flows: Optional[OAuthFlows] = None
+ """
+ **REQUIRED** for `oauth2`. An object containing configuration information for the
+ flow types supported.
+ """
+
+ openIdConnectUrl: Optional[str] = None
+ """
+ **REQUIRED** for `openIdConnect`. OpenId Connect URL to discover OAuth2
+ configuration values. This MUST be in the form of a URL.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/server.py b/openapi_pydantic/v3/v3_0/server.py
new file mode 100644
index 0000000..104ba5b
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/server.py
@@ -0,0 +1,67 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .server_variable import ServerVariable
+
+_examples = [
+ {
+ "url": "https://development.gigantic-server.com/v1",
+ "description": "Development server",
+ },
+ {
+ "url": "https://{username}.gigantic-server.com:{port}/{basePath}",
+ "description": "The production API server",
+ "variables": {
+ "username": {
+ "default": "demo",
+ "description": "this value is assigned by the service"
+ "provider, in this example `gigantic-server.com`",
+ },
+ "port": {"enum": ["8443", "443"], "default": "8443"},
+ "basePath": {"default": "v2"},
+ },
+ },
+]
+
+
+class Server(BaseModel):
+ """An object representing a Server."""
+
+ url: str
+ """
+ **REQUIRED**. A URL to the target host.
+
+ This URL supports Server Variables and MAY be relative,
+ to indicate that the host location is relative to the location where the OpenAPI
+ document is being served.
+ Variable substitutions will be made when a variable is named in `{`brackets`}`.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional string describing the host designated by the URL.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ variables: Optional[Dict[str, ServerVariable]] = None
+ """
+ A map between a variable name and its value.
+
+ The value is used for substitution in the server's URL template.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/server_variable.py b/openapi_pydantic/v3/v3_0/server_variable.py
new file mode 100644
index 0000000..1b6b57e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/server_variable.py
@@ -0,0 +1,42 @@
+from typing import List, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+
+class ServerVariable(BaseModel):
+ """An object representing a Server Variable for server URL template substitution."""
+
+ enum: Optional[List[str]] = None
+ """
+ An enumeration of string values to be used if the substitution options are from a
+ limited set. The array SHOULD NOT be empty.
+ """
+
+ default: str
+ """
+ **REQUIRED**. The default value to use for substitution,
+ which SHALL be sent if an alternate value is _not_ supplied.
+ Note this behavior is different than the [Schema Object's](#schemaObject) treatment
+ of default values, because in those cases parameter values are optional.
+ If the [`enum`](#serverVariableEnum) is defined, the value SHOULD exist in the
+ enum's values.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional description for the server variable.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_0/tag.py b/openapi_pydantic/v3/v3_0/tag.py
new file mode 100644
index 0000000..9ec0d03
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/tag.py
@@ -0,0 +1,47 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .external_documentation import ExternalDocumentation
+
+_examples = [{"name": "pet", "description": "Pets operations"}]
+
+
+class Tag(BaseModel):
+ """
+ Adds metadata to a single tag that is used by the
+ [Operation Object](#operationObject).
+ It is not mandatory to have a Tag Object per tag defined in the Operation Object
+ instances.
+ """
+
+ name: str
+ """
+ **REQUIRED**. The name of the tag.
+ """
+
+ description: Optional[str] = None
+ """
+ A short description for the tag.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this tag.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_0/util.py b/openapi_pydantic/v3/v3_0/util.py
new file mode 100644
index 0000000..1b904b5
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/util.py
@@ -0,0 +1,260 @@
+import logging
+import re
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ Generic,
+ List,
+ Optional,
+ Set,
+ Type,
+ TypeVar,
+ Union,
+ cast,
+)
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import (
+ DEFS_KEY,
+ PYDANTIC_V2,
+ JsonSchemaMode,
+ models_json_schema,
+ v1_schema,
+)
+
+from . import Components, OpenAPI, Reference, Schema, schema_validate
+
+logger = logging.getLogger(__name__)
+
+PydanticType = TypeVar("PydanticType", bound=BaseModel)
+ref_prefix = "#/components/schemas/"
+ref_template = "#/components/schemas/{model}"
+
+
+class PydanticSchema(Schema, Generic[PydanticType]):
+ """Special `Schema` class to indicate a reference from pydantic class"""
+
+ schema_class: Type[PydanticType]
+ """the class that is used for generate the schema"""
+
+
+def get_mode(
+ cls: Type[BaseModel], default: JsonSchemaMode = "validation"
+) -> JsonSchemaMode:
+ """Get the JSON schema mode for a model class.
+
+ The mode can be either "validation" or "serialization". In validation mode,
+ computed fields are dropped and optional fields remain optional. In
+ serialization mode, computed and optional fields are required.
+ """
+ if not hasattr(cls, "model_config"):
+ return default
+ mode = cls.model_config.get("json_schema_mode", default)
+ if mode not in ("validation", "serialization"):
+ raise ValueError(f"invalid json_schema_mode: {mode}")
+ return cast(JsonSchemaMode, mode)
+
+
+if TYPE_CHECKING:
+
+ class GenerateOpenAPI30Schema: ...
+
+elif PYDANTIC_V2:
+ from enum import Enum
+
+ from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
+ from pydantic_core import core_schema
+
+ class GenerateOpenAPI30Schema(GenerateJsonSchema):
+ """Modify the schema generation for OpenAPI 3.0."""
+
+ def nullable_schema(
+ self,
+ schema: core_schema.NullableSchema,
+ ) -> JsonSchemaValue:
+ """Generates a JSON schema that matches a schema that allows null values.
+
+ In OpenAPI 3.0, types can not be None, but a special "nullable" field is
+ available.
+ """
+ inner_json_schema = self.generate_inner(schema["schema"])
+ inner_json_schema["nullable"] = True
+ return inner_json_schema
+
+ def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue:
+ """Generates a JSON schema that matches a literal value.
+
+ In OpenAPI 3.0, the "const" keyword is not supported, so this
+ version of this method skips that optimization.
+ """
+ expected = [
+ v.value if isinstance(v, Enum) else v for v in schema["expected"]
+ ]
+
+ types = {type(e) for e in expected}
+ if types == {str}:
+ return {"enum": expected, "type": "string"}
+ elif types == {int}:
+ return {"enum": expected, "type": "integer"}
+ elif types == {float}:
+ return {"enum": expected, "type": "number"}
+ elif types == {bool}:
+ return {"enum": expected, "type": "boolean"}
+ elif types == {list}:
+ return {"enum": expected, "type": "array"}
+ # there is not None case because if it's mixed it hits the final `else`
+ # if it's a single Literal[None] then it becomes a `const` schema above
+ else:
+ return {"enum": expected}
+
+else:
+
+ class GenerateOpenAPI30Schema: ...
+
+
+def construct_open_api_with_schema_class(
+ open_api: OpenAPI,
+ schema_classes: Optional[List[Type[BaseModel]]] = None,
+ scan_for_pydantic_schema_reference: bool = True,
+ by_alias: bool = True,
+) -> OpenAPI:
+ """
+ Construct a new OpenAPI object, utilising pydantic classes to produce JSON schemas.
+
+ :param open_api: the base `OpenAPI` object
+ :param schema_classes: Pydantic classes that their schema will be used
+ "#/components/schemas" values
+ :param scan_for_pydantic_schema_reference: flag to indicate if scanning for
+ `PydanticSchemaReference` class
+ is needed for "#/components/schemas"
+ value updates
+ :param by_alias: construct schema by alias (default is True)
+ :return: new OpenAPI object with "#/components/schemas" values updated.
+ If there is no update in "#/components/schemas" values, the original
+ `open_api` will be returned.
+ """
+ copy_func = getattr(open_api, "model_copy" if PYDANTIC_V2 else "copy")
+ new_open_api: OpenAPI = copy_func(deep=True)
+
+ if scan_for_pydantic_schema_reference:
+ extracted_schema_classes = _handle_pydantic_schema(new_open_api)
+ if schema_classes:
+ schema_classes = list({*schema_classes, *extracted_schema_classes})
+ else:
+ schema_classes = extracted_schema_classes
+
+ if not schema_classes:
+ return open_api
+
+ schema_classes.sort(key=lambda x: x.__name__)
+ logger.debug("schema_classes: %s", schema_classes)
+
+ # update new_open_api with new #/components/schemas
+ if PYDANTIC_V2:
+ _key_map, schema_definitions = models_json_schema(
+ [(c, get_mode(c)) for c in schema_classes],
+ by_alias=by_alias,
+ ref_template=ref_template,
+ schema_generator=GenerateOpenAPI30Schema,
+ )
+ else:
+ schema_definitions = v1_schema(
+ schema_classes, by_alias=by_alias, ref_prefix=ref_prefix
+ )
+
+ if not new_open_api.components:
+ new_open_api.components = Components()
+ if new_open_api.components.schemas:
+ for existing_key in new_open_api.components.schemas:
+ if existing_key in schema_definitions[DEFS_KEY]:
+ logger.warning(
+ f'"{existing_key}" already exists in {ref_prefix}. '
+ f'The value of "{ref_prefix}{existing_key}" will be overwritten.'
+ )
+ new_open_api.components.schemas.update(_validate_schemas(schema_definitions))
+ else:
+ new_open_api.components.schemas = _validate_schemas(schema_definitions)
+ return new_open_api
+
+
+def _validate_schemas(
+ schema_definitions: Dict[str, Any]
+) -> Dict[str, Union[Reference, Schema]]:
+ """Convert JSON Schema definitions to parsed OpenAPI objects"""
+ # Note: if an error occurs in schema_validate(), it may indicate that
+ # the generated JSON schemas are not compatible with the version
+ # of OpenAPI this module depends on.
+ return {
+ key: schema_validate(schema_dict)
+ for key, schema_dict in schema_definitions[DEFS_KEY].items()
+ }
+
+
+def _handle_pydantic_schema(open_api: OpenAPI) -> List[Type[BaseModel]]:
+ """
+ This function traverses the `OpenAPI` object and
+
+ 1. Replaces the `PydanticSchema` object with `Reference` object, with correct ref
+ value;
+ 2. Extracts the involved schema class from `PydanticSchema` object.
+
+ **This function will mutate the input `OpenAPI` object.**
+
+ :param open_api: the `OpenAPI` object to be traversed and mutated
+ :return: a list of schema classes extracted from `PydanticSchema` objects
+ """
+
+ pydantic_types: Set[Type[BaseModel]] = set()
+
+ def _traverse(obj: Any) -> None:
+ if isinstance(obj, BaseModel):
+ fields = getattr(
+ obj, "model_fields_set" if PYDANTIC_V2 else "__fields_set__"
+ )
+ for field in fields:
+ child_obj = obj.__getattribute__(field)
+ if isinstance(child_obj, PydanticSchema):
+ logger.debug("PydanticSchema found in %s: %s", obj, child_obj)
+ obj.__setattr__(field, _construct_ref_obj(child_obj))
+ pydantic_types.add(child_obj.schema_class)
+ else:
+ _traverse(child_obj)
+ elif isinstance(obj, list):
+ for index, elem in enumerate(obj):
+ if isinstance(elem, PydanticSchema):
+ logger.debug(f"PydanticSchema found in list: {elem}")
+ obj[index] = _construct_ref_obj(elem)
+ pydantic_types.add(elem.schema_class)
+ else:
+ _traverse(elem)
+ elif isinstance(obj, dict):
+ for key, value in obj.items():
+ if isinstance(value, PydanticSchema):
+ logger.debug(f"PydanticSchema found in dict: {value}")
+ obj[key] = _construct_ref_obj(value)
+ pydantic_types.add(value.schema_class)
+ else:
+ _traverse(value)
+
+ _traverse(open_api)
+ return list(pydantic_types)
+
+
+def _construct_ref_obj(pydantic_schema: PydanticSchema[PydanticType]) -> Reference:
+ """
+ Construct a reference object from the Pydantic schema name
+
+ characters in the schema name that are invalid/problematic
+ for JSONschema $ref names will get replaced with underscores.
+ Especially needed for Pydantic generic Models with brackets "[]"
+
+ see: https://github.com/pydantic/pydantic/blob/aee6057378ccfec02126bf9c984a9b6d6b411777/pydantic/json_schema.py#L2031
+ """
+ ref_name = re.sub(
+ r"[^a-zA-Z0-9.\-_]", "_", pydantic_schema.schema_class.__name__
+ ).replace(".", "__")
+ ref_obj = Reference(**{"$ref": ref_prefix + ref_name})
+ logger.debug(f"ref_obj={ref_obj}")
+ return ref_obj
diff --git a/openapi_pydantic/v3/v3_0/xml.py b/openapi_pydantic/v3/v3_0/xml.py
new file mode 100644
index 0000000..25775bf
--- /dev/null
+++ b/openapi_pydantic/v3/v3_0/xml.py
@@ -0,0 +1,68 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {"namespace": "http://example.com/schema/sample", "prefix": "sample"},
+ {"name": "aliens", "wrapped": True},
+]
+
+
+class XML(BaseModel):
+ """
+ A metadata object that allows for more fine-tuned XML model definitions.
+
+ When using arrays, XML element names are *not* inferred (for singular/plural forms)
+ and the `name` property SHOULD be used to add that information.
+ See examples for expected behavior.
+ """
+
+ name: Optional[str] = None
+ """
+ Replaces the name of the element/attribute used for the described schema property.
+ When defined within `items`, it will affect the name of the individual XML elements
+ within the list. When defined alongside `type` being `array` (outside the `items`),
+ it will affect the wrapping element and only if `wrapped` is `true`.
+ If `wrapped` is `false`, it will be ignored.
+ """
+
+ namespace: Optional[str] = None
+ """
+ The URI of the namespace definition.
+ Value MUST be in the form of an absolute URI.
+ """
+
+ prefix: Optional[str] = None
+ """
+ The prefix to be used for the [name](#xmlName).
+ """
+
+ attribute: bool = False
+ """
+ Declares whether the property definition translates to an attribute instead of an
+ element. Default value is `false`.
+ """
+
+ wrapped: bool = False
+ """
+ MAY be used only for an array definition.
+ Signifies whether the array is wrapped (for example,
+ ``) or unwrapped (``).
+ Default value is `false`.
+ The definition takes effect only when defined alongside `type` being `array`
+ (outside the `items`).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/README.md b/openapi_pydantic/v3/v3_1/README.md
new file mode 100644
index 0000000..2232c46
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/README.md
@@ -0,0 +1,41 @@
+# OpenAPI v3.1 schema classes
+
+## Alias
+
+Due to the reserved words in python and pydantic,
+the following fields are used with [alias](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) feature provided by pydantic:
+
+| Class | Field name in the class | Alias (as in OpenAPI spec) |
+| ----- | ----------------------- | -------------------------- |
+| Header[*](#header_param_in) | param_in | in |
+| MediaType | media_type_schema | schema |
+| Parameter | param_in | in |
+| Parameter | param_schema | schema |
+| PathItem | ref | $ref |
+| Reference | ref | $ref |
+| SecurityScheme | security_scheme_in | in |
+| Schema | schema_format | format |
+| Schema | schema_not | not |
+| Schema | schema_if | if |
+| Schema | schema_else | else |
+
+> The "in" field in Header object is actually a constant (`{"in": "header"}`).
+
+> For convenience of object creation, the classes mentioned in above
+> have configured `allow_population_by_field_name=True` (Pydantic V1) or `populate_by_name=True` (Pydantic V2).
+>
+> Reference: [Pydantic's Model Config](https://pydantic-docs.helpmanual.io/usage/model_config/)
+
+## Non-pydantic schema types
+
+Due to the constriants of python typing structure (not able to handle dynamic field names),
+the following schema classes are actually just a typing of `Dict`:
+
+| Schema Type | Implementation |
+| ----------- | -------------- |
+| Callback | `Callback = Dict[str, PathItem]` |
+| Paths | `Paths = Dict[str, PathItem]` |
+| Responses | `Responses = Dict[str, Union[Response, Reference]]` |
+| SecurityRequirement | `SecurityRequirement = Dict[str, List[str]]` |
+
+On creating such schema instances, please use python's `dict` type instead to instantiate.
diff --git a/openapi_pydantic/v3/v3_1/__init__.py b/openapi_pydantic/v3/v3_1/__init__.py
new file mode 100644
index 0000000..0574504
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/__init__.py
@@ -0,0 +1,59 @@
+"""
+OpenAPI v3.1 schema types, created according to the specification:
+https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.1.md
+
+The type orders are according to the contents of the specification:
+https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.1.md#table-of-contents
+"""
+
+from typing import TYPE_CHECKING
+
+from openapi_pydantic.compat import PYDANTIC_V2
+
+from .callback import Callback as Callback
+from .components import Components as Components
+from .contact import Contact as Contact
+from .datatype import DataType as DataType
+from .discriminator import Discriminator as Discriminator
+from .encoding import Encoding as Encoding
+from .example import Example as Example
+from .external_documentation import ExternalDocumentation as ExternalDocumentation
+from .header import Header as Header
+from .info import Info as Info
+from .license import License as License
+from .link import Link as Link
+from .media_type import MediaType as MediaType
+from .oauth_flow import OAuthFlow as OAuthFlow
+from .oauth_flows import OAuthFlows as OAuthFlows
+from .open_api import OpenAPI as OpenAPI
+from .operation import Operation as Operation
+from .parameter import Parameter as Parameter
+from .parameter import ParameterLocation as ParameterLocation
+from .path_item import PathItem as PathItem
+from .paths import Paths as Paths
+from .reference import Reference as Reference
+from .request_body import RequestBody as RequestBody
+from .response import Response as Response
+from .responses import Responses as Responses
+from .schema import Schema as Schema
+from .schema import schema_validate as schema_validate
+from .security_requirement import SecurityRequirement as SecurityRequirement
+from .security_scheme import SecurityScheme as SecurityScheme
+from .server import Server as Server
+from .server_variable import ServerVariable as ServerVariable
+from .tag import Tag as Tag
+from .xml import XML as XML
+
+if TYPE_CHECKING:
+ pass
+elif PYDANTIC_V2:
+ # resolve forward references
+ Encoding.model_rebuild()
+ OpenAPI.model_rebuild()
+ Components.model_rebuild()
+ Operation.model_rebuild()
+else:
+ # resolve forward references
+ Encoding.update_forward_refs(Header=Header)
+ Schema.update_forward_refs()
+ Operation.update_forward_refs(PathItem=PathItem)
diff --git a/openapi_pydantic/v3/v3_1/callback.py b/openapi_pydantic/v3/v3_1/callback.py
new file mode 100644
index 0000000..9b8b4b9
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/callback.py
@@ -0,0 +1,26 @@
+from typing import TYPE_CHECKING, Dict, Union
+
+from .reference import Reference
+
+if TYPE_CHECKING:
+ from .path_item import PathItem
+
+
+Callback = Dict[str, Union["PathItem", Reference]]
+"""
+A map of possible out-of band callbacks related to the parent operation.
+Each value in the map is a [Path Item Object](#pathItemObject)
+that describes a set of requests that may be initiated by the API provider and the
+expected responses. The key value used to identify the path item object is an
+expression, evaluated at runtime, that identifies a URL to use for the callback
+operation.
+"""
+
+"""Patterned Fields"""
+
+# {expression}: 'PathItem' = ...
+"""
+A Path Item Object used to define a callback request and expected responses.
+
+A [complete example](../examples/v3.0/callback-example.yaml) is available.
+"""
diff --git a/openapi_pydantic/v3/v3_1/components.py b/openapi_pydantic/v3/v3_1/components.py
new file mode 100644
index 0000000..956ae03
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/components.py
@@ -0,0 +1,142 @@
+from typing import Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .callback import Callback
+from .example import Example
+from .header import Header
+from .link import Link
+from .parameter import Parameter
+from .path_item import PathItem
+from .reference import Reference
+from .request_body import RequestBody
+from .response import Response
+from .schema import Schema
+from .security_scheme import SecurityScheme
+
+_examples = [
+ {
+ "schemas": {
+ "GeneralError": {
+ "type": "object",
+ "properties": {
+ "code": {"type": "integer", "format": "int32"},
+ "message": {"type": "string"},
+ },
+ },
+ "Category": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ },
+ "Tag": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ },
+ },
+ "parameters": {
+ "skipParam": {
+ "name": "skip",
+ "in": "query",
+ "description": "number of items to skip",
+ "required": True,
+ "schema": {"type": "integer", "format": "int32"},
+ },
+ "limitParam": {
+ "name": "limit",
+ "in": "query",
+ "description": "max records to return",
+ "required": True,
+ "schema": {"type": "integer", "format": "int32"},
+ },
+ },
+ "responses": {
+ "NotFound": {"description": "Entity not found."},
+ "IllegalInput": {"description": "Illegal input for operation."},
+ "GeneralError": {
+ "description": "General Error",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/GeneralError"}
+ }
+ },
+ },
+ },
+ "securitySchemes": {
+ "api_key": {
+ "type": "apiKey",
+ "name": "api_key",
+ "in": "header",
+ },
+ "petstore_auth": {
+ "type": "oauth2",
+ "flows": {
+ "implicit": {
+ "authorizationUrl": "http://example.org/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ }
+ },
+ },
+ },
+ }
+]
+
+
+class Components(BaseModel):
+ """
+ Holds a set of reusable objects for different aspects of the OAS.
+ All objects defined within the components object will have no effect on the API
+ unless they are explicitly referenced from properties outside the components object.
+ """
+
+ schemas: Optional[Dict[str, Schema]] = None
+ """An object to hold reusable [Schema Objects](#schemaObject)."""
+
+ responses: Optional[Dict[str, Union[Response, Reference]]] = None
+ """An object to hold reusable [Response Objects](#responseObject)."""
+
+ parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None
+ """An object to hold reusable [Parameter Objects](#parameterObject)."""
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """An object to hold reusable [Example Objects](#exampleObject)."""
+
+ requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None
+ """An object to hold reusable [Request Body Objects](#requestBodyObject)."""
+
+ headers: Optional[Dict[str, Union[Header, Reference]]] = None
+ """An object to hold reusable [Header Objects](#headerObject)."""
+
+ securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
+ """An object to hold reusable [Security Scheme Objects](#securitySchemeObject)."""
+
+ links: Optional[Dict[str, Union[Link, Reference]]] = None
+ """An object to hold reusable [Link Objects](#linkObject)."""
+
+ callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None
+ """An object to hold reusable [Callback Objects](#callbackObject)."""
+
+ pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None
+ """An object to hold reusable [Path Item Object](#pathItemObject)."""
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/contact.py b/openapi_pydantic/v3/v3_1/contact.py
new file mode 100644
index 0000000..5b11d3a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/contact.py
@@ -0,0 +1,48 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "name": "API Support",
+ "url": "http://www.example.com/support",
+ "email": "support@example.com",
+ }
+]
+
+
+class Contact(BaseModel):
+ """
+ Contact information for the exposed API.
+ """
+
+ name: Optional[str] = None
+ """
+ The identifying name of the contact person/organization.
+ """
+
+ url: Optional[str] = None
+ """
+ The URL pointing to the contact information.
+ MUST be in the form of a URL.
+ """
+
+ email: Optional[str] = None
+ """
+ The email address of the contact person/organization.
+ MUST be in the form of an email address.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/datatype.py b/openapi_pydantic/v3/v3_1/datatype.py
new file mode 100644
index 0000000..6477eee
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/datatype.py
@@ -0,0 +1,13 @@
+from enum import Enum
+
+
+class DataType(str, Enum):
+ """Data type of an object."""
+
+ NULL = "null"
+ STRING = "string"
+ NUMBER = "number"
+ INTEGER = "integer"
+ BOOLEAN = "boolean"
+ ARRAY = "array"
+ OBJECT = "object"
diff --git a/openapi_pydantic/v3/v3_1/discriminator.py b/openapi_pydantic/v3/v3_1/discriminator.py
new file mode 100644
index 0000000..1348906
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/discriminator.py
@@ -0,0 +1,52 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "#/components/schemas/Dog",
+ "monster": "https://gigantic-server.com/schemas/Monster/schema.json",
+ },
+ }
+]
+
+
+class Discriminator(BaseModel):
+ """
+ When request bodies or response payloads may be one of a number of different
+ schemas, a `discriminator` object can be used to aid in serialization,
+ deserialization, and validation.
+
+ The discriminator is a specific object in a schema which is used to inform the
+ consumer of the specification of an alternative schema based on the value
+ associated with it.
+
+ 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.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/encoding.py b/openapi_pydantic/v3/v3_1/encoding.py
new file mode 100644
index 0000000..580d3d1
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/encoding.py
@@ -0,0 +1,101 @@
+from typing import TYPE_CHECKING, Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .reference import Reference
+
+if TYPE_CHECKING:
+ from .header import Header
+
+_examples = [
+ {
+ "contentType": "image/png, image/jpeg",
+ "headers": {
+ "X-Rate-Limit-Limit": {
+ "description": "The number of allowed requests in the "
+ "current period",
+ "schema": {"type": "integer"},
+ }
+ },
+ }
+]
+
+
+class Encoding(BaseModel):
+ """A single encoding definition applied to a single schema property."""
+
+ contentType: Optional[str] = None
+ """
+ The Content-Type for encoding a specific property.
+ Default value depends on the property type:
+
+ for `object` - `application/json`;
+ for `array` – the default is defined based on the inner type;
+ for all other cases the default is `application/octet-stream`.
+
+ The value can be a specific media type (e.g. `application/json`), a wildcard media
+ type (e.g. `image/*`), or a comma-separated list of the two types.
+ """
+
+ headers: Optional[Dict[str, Union["Header", Reference]]] = None
+ """
+ A map allowing additional information to be provided as headers, for example
+ `Content-Disposition`.
+
+ `Content-Type` is described separately and SHALL be ignored in this section.
+ This property SHALL be ignored if the request body media type is not a `multipart`.
+ """
+
+ style: Optional[str] = None
+ """
+ Describes how a specific property value will be serialized depending on its type.
+
+ See [Parameter Object](#parameterObject) for details on the
+ [`style`](#parameterStyle) property. The behavior follows the same values as
+ `query` parameters, including default values.
+ This property SHALL be ignored if the request body media type
+ is not `application/x-www-form-urlencoded` or `multipart/form-data`.
+ If a value is explicitly defined, then the value of
+ [`contentType`](#encodingContentType) (implicit or explicit) SHALL be ignored.
+ """
+
+ explode: Optional[bool] = None
+ """
+ When this is true, property values of type `array` or `object` generate separate
+ parameters for each value of the array, or key-value-pair of the map.
+
+ For other types of properties this property has no effect.
+ When [`style`](#encodingStyle) is `form`, the default value is `true`.
+ For all other styles, the default value is `false`.
+ This property SHALL be ignored if the request body media type
+ is not `application/x-www-form-urlencoded` or `multipart/form-data`.
+ If a value is explicitly defined, then the value of
+ [`contentType`](#encodingContentType) (implicit or explicit) SHALL be ignored.
+ """
+
+ allowReserved: bool = False
+ """
+ Determines whether the parameter value SHOULD allow reserved characters,
+ as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
+ `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
+ The default value is `false`.
+ This property SHALL be ignored if the request body media type
+ is not `application/x-www-form-urlencoded` or `multipart/form-data`.
+ If a value is explicitly defined,
+ then the value of [`contentType`](#encodingContentType) (implicit or explicit)
+ SHALL be ignored.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/example.py b/openapi_pydantic/v3/v3_1/example.py
new file mode 100644
index 0000000..7d4cf4e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/example.py
@@ -0,0 +1,64 @@
+from typing import Any, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, 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",
+ },
+]
+
+
+class Example(BaseModel):
+ summary: Optional[str] = None
+ """
+ Short description for the example.
+ """
+
+ description: Optional[str] = None
+ """
+ Long description for the example.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ value: Optional[Any] = None
+ """
+ Embedded literal example.
+ The `value` field and `externalValue` field are mutually exclusive.
+ To represent examples of media types that cannot naturally represented in JSON or
+ YAML, use a string value to contain the example, escaping where necessary.
+ """
+
+ externalValue: Optional[str] = None
+ """
+ A URL that points to the literal example.
+ This provides the capability to reference examples that cannot easily be included
+ in JSON or YAML documents.
+
+ The `value` field and `externalValue` field are mutually exclusive.
+ See the rules for resolving [Relative References](#relativeReferencesURI).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/external_documentation.py b/openapi_pydantic/v3/v3_1/external_documentation.py
new file mode 100644
index 0000000..b88e8b2
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/external_documentation.py
@@ -0,0 +1,36 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [{"description": "Find more info here", "url": "https://example.com"}]
+
+
+class ExternalDocumentation(BaseModel):
+ """Allows referencing an external resource for extended documentation."""
+
+ description: Optional[str] = None
+ """
+ A short description of the target documentation.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ url: str
+ """
+ **REQUIRED**. The URL for the target documentation.
+ Value MUST be in the form of a URL.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/header.py b/openapi_pydantic/v3/v3_1/header.py
new file mode 100644
index 0000000..451d77e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/header.py
@@ -0,0 +1,37 @@
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .parameter import ParameterBase
+
+_examples = [
+ {
+ "description": "The number of allowed requests in the current period",
+ "schema": {"type": "integer"},
+ }
+]
+
+
+class Header(ParameterBase):
+ """
+ The Header Object follows the structure of the
+ [Parameter Object](#parameterObject) with the following changes:
+
+ 1. `name` MUST NOT be specified, it is given in the corresponding
+ `headers` map.
+ 2. `in` MUST NOT be specified, it is implicitly in `header`.
+ 3. All traits that are affected by the location MUST be applicable
+ to a location of `header` (for example, [`style`](#parameterStyle)).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/info.py b/openapi_pydantic/v3/v3_1/info.py
new file mode 100644
index 0000000..f27ef12
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/info.py
@@ -0,0 +1,87 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .contact import Contact
+from .license import License
+
+_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",
+ }
+]
+
+
+class Info(BaseModel):
+ """
+ The object provides metadata about the API.
+ The metadata MAY be used by the clients if needed,
+ and MAY be presented in editing or documentation generation tools for convenience.
+ """
+
+ title: str
+ """
+ **REQUIRED**. The title of the API.
+ """
+
+ summary: Optional[str] = None
+ """
+ A short summary of the API.
+ """
+
+ description: Optional[str] = None
+ """
+ A description of the API.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ termsOfService: Optional[str] = None
+ """
+ A URL to the Terms of Service for the API.
+ MUST be in the form of a URL.
+ """
+
+ contact: Optional[Contact] = None
+ """
+ The contact information for the exposed API.
+ """
+
+ license: Optional[License] = None
+ """
+ The license information for the exposed API.
+ """
+
+ version: str
+ """
+ **REQUIRED**. The version of the OpenAPI document
+ (which is distinct from the [OpenAPI Specification version](#oasVersion) or the API
+ implementation version).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/license.py b/openapi_pydantic/v3/v3_1/license.py
new file mode 100644
index 0000000..58d103e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/license.py
@@ -0,0 +1,50 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {"name": "Apache 2.0", "identifier": "Apache-2.0"},
+ {
+ "name": "Apache 2.0",
+ "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
+ },
+]
+
+
+class License(BaseModel):
+ """
+ License information for the exposed API.
+ """
+
+ name: str
+ """
+ **REQUIRED**. The license name used for the API.
+ """
+
+ identifier: Optional[str] = None
+ """
+ An [SPDX](https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60)
+ license expression for the API. The `identifier` field is mutually exclusive of the
+ `url` field.
+ """
+
+ url: Optional[str] = None
+ """
+ A URL to the license used for the API.
+ This MUST be in the form of a URL.
+ The `url` field is mutually exclusive of the `identifier` field.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/link.py b/openapi_pydantic/v3/v3_1/link.py
new file mode 100644
index 0000000..c6dec93
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/link.py
@@ -0,0 +1,95 @@
+from typing import Any, Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .server import Server
+
+_examples = [
+ {
+ "operationId": "getUserAddressByUUID",
+ "parameters": {"userUuid": "$response.body#/uuid"},
+ },
+ {
+ "operationRef": "#/paths/~12.0~1repositories~1{username}/get",
+ "parameters": {"username": "$response.body#/username"},
+ },
+]
+
+
+class Link(BaseModel):
+ """
+ The `Link object` represents a possible design-time link for a response.
+ The presence of a link does not guarantee the caller's ability to successfully
+ invoke it, rather it provides a known relationship and traversal mechanism between
+ responses and other operations.
+
+ Unlike _dynamic_ links (i.e. links provided **in** the response payload),
+ the OAS linking mechanism does not require link information in the runtime response.
+
+ For computing links, and providing instructions to execute them,
+ a [runtime expression](#runtimeExpression) is used for accessing values in an
+ operation and using them as parameters while invoking the linked operation.
+ """
+
+ operationRef: Optional[str] = None
+ """
+ A relative or absolute URI reference to an OAS operation.
+ This field is mutually exclusive of the `operationId` field,
+ and MUST point to an [Operation Object](#operationObject).
+ Relative `operationRef` values MAY be used to locate an existing
+ [Operation Object](#operationObject) in the OpenAPI definition. See the rules for
+ resolving [Relative References](#relativeReferencesURI).
+ """
+
+ operationId: Optional[str] = None
+ """
+ The name of an _existing_, resolvable OAS operation, as defined with a unique
+ `operationId`.
+
+ This field is mutually exclusive of the `operationRef` field.
+ """
+
+ parameters: Optional[Dict[str, Any]] = None
+ """
+ A map representing parameters to pass to an operation
+ as specified with `operationId` or identified via `operationRef`.
+ The key is the parameter name to be used,
+ whereas the value can be a constant or an expression to be evaluated and passed to
+ the linked operation.
+
+ The parameter name can be qualified using the [parameter location](#parameterIn)
+ `[{in}.]{name}` for operations that use the same parameter name in different
+ locations (e.g. path.id).
+ """
+
+ requestBody: Optional[Any] = None
+ """
+ A literal value or [{expression}](#runtimeExpression) to use as a request body when
+ calling the target operation.
+ """
+
+ description: Optional[str] = None
+ """
+ A description of the link.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ server: Optional[Server] = None
+ """
+ A server object to be used by the target operation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/media_type.py b/openapi_pydantic/v3/v3_1/media_type.py
new file mode 100644
index 0000000..0953596
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/media_type.py
@@ -0,0 +1,97 @@
+from typing import Any, Dict, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .encoding import Encoding
+from .example import Example
+from .reference import Reference
+from .schema import Schema
+
+_examples = [
+ {
+ "schema": {"$ref": "#/components/schemas/Pet"},
+ "examples": {
+ "cat": {
+ "summary": "An example of a cat",
+ "value": {
+ "name": "Fluffy",
+ "petType": "Cat",
+ "color": "White",
+ "gender": "male",
+ "breed": "Persian",
+ },
+ },
+ "dog": {
+ "summary": "An example of a dog with a cat's name",
+ "value": {
+ "name": "Puma",
+ "petType": "Dog",
+ "color": "Black",
+ "gender": "Female",
+ "breed": "Mixed",
+ },
+ },
+ "frog": {"$ref": "#/components/examples/frog-example"},
+ },
+ }
+]
+
+
+class MediaType(BaseModel):
+ """Each Media Type Object provides schema and examples for the media type
+ identified by its key."""
+
+ media_type_schema: Optional[Union[Reference, Schema]] = Field(
+ default=None, alias="schema"
+ )
+ """
+ The schema defining the content of the request, response, or parameter.
+ """
+
+ example: Optional[Any] = None
+ """
+ Example of the media type.
+
+ The example object SHOULD be in the correct format as specified by the media type.
+
+ The `example` field is mutually exclusive of the `examples` field.
+
+ Furthermore, if referencing a `schema` which contains an example,
+ the `example` value SHALL _override_ the example provided by the schema.
+ """
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """
+ Examples of the media type.
+
+ Each example object SHOULD match the media type and specified schema if present.
+
+ The `examples` field is mutually exclusive of the `example` field.
+
+ Furthermore, if referencing a `schema` which contains an example,
+ the `examples` value SHALL _override_ the example provided by the schema.
+ """
+
+ encoding: Optional[Dict[str, Encoding]] = None
+ """
+ A map between a property name and its encoding information.
+ The key, being the property name, MUST exist in the schema as a property.
+ The encoding object SHALL only apply to `requestBody` objects
+ when the media type is `multipart` or `application/x-www-form-urlencoded`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/oauth_flow.py b/openapi_pydantic/v3/v3_1/oauth_flow.py
new file mode 100644
index 0000000..acf741e
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/oauth_flow.py
@@ -0,0 +1,80 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+ {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "tokenUrl": "https://example.com/api/oauth/token",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+ {
+ "authorizationUrl": "/api/oauth/dialog",
+ "tokenUrl": "/api/oauth/token",
+ "refreshUrl": "/api/oauth/token",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ },
+]
+
+
+class OAuthFlow(BaseModel):
+ """
+ Configuration details for a supported OAuth Flow
+ """
+
+ authorizationUrl: Optional[str] = None
+ """
+ **REQUIRED** for `oauth2 ("implicit", "authorizationCode")`.
+ The authorization URL to be used for this flow.
+ This MUST be in the form of a URL.
+ The OAuth2 standard requires the use of TLS.
+ """
+
+ tokenUrl: Optional[str] = None
+ """
+ **REQUIRED** for `oauth2 ("password", "clientCredentials", "authorizationCode")`.
+ The token URL to be used for this flow.
+ This MUST be in the form of a URL.
+ The OAuth2 standard requires the use of TLS.
+ """
+
+ refreshUrl: Optional[str] = None
+ """
+ The URL to be used for obtaining refresh tokens.
+ This MUST be in the form of a URL.
+ The OAuth2 standard requires the use of TLS.
+ """
+
+ scopes: Optional[Dict[str, str]] = None
+ """
+ **REQUIRED** for `oauth2`. The available scopes for the OAuth2 security scheme.
+ A map between the scope name and a short description for it.
+ The map MAY be empty.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/oauth_flows.py b/openapi_pydantic/v3/v3_1/oauth_flows.py
new file mode 100644
index 0000000..c8a8e5a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/oauth_flows.py
@@ -0,0 +1,47 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .oauth_flow import OAuthFlow
+
+
+class OAuthFlows(BaseModel):
+ """
+ Allows configuration of the supported OAuth Flows.
+ """
+
+ implicit: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Implicit flow
+ """
+
+ password: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Resource Owner Password flow
+ """
+
+ clientCredentials: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Client Credentials flow.
+
+ Previously called `application` in OpenAPI 2.0.
+ """
+
+ authorizationCode: Optional[OAuthFlow] = None
+ """
+ Configuration for the OAuth Authorization Code flow.
+
+ Previously called `accessCode` in OpenAPI 2.0.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_1/open_api.py b/openapi_pydantic/v3/v3_1/open_api.py
new file mode 100644
index 0000000..3427dc5
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/open_api.py
@@ -0,0 +1,104 @@
+from typing import Dict, List, Literal, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .components import Components
+from .external_documentation import ExternalDocumentation
+from .info import Info
+from .path_item import PathItem
+from .paths import Paths
+from .reference import Reference
+from .security_requirement import SecurityRequirement
+from .server import Server
+from .tag import Tag
+
+
+class OpenAPI(BaseModel):
+ """This is the root document object of the OpenAPI document."""
+
+ openapi: Literal["3.1.1", "3.1.0"] = "3.1.1"
+ """
+ **REQUIRED**. This string MUST be the [version number](#versions)
+ of the OpenAPI Specification that the OpenAPI document uses.
+ The `openapi` field SHOULD be used by tooling to interpret the OpenAPI document.
+ This is *not* related to the API [`info.version`](#infoVersion) string.
+ """
+
+ info: Info
+ """
+ **REQUIRED**. Provides metadata about the API. The metadata MAY be used by tooling
+ as required.
+ """
+
+ jsonSchemaDialect: Optional[str] = None
+ """
+ The default value for the `$schema` keyword within [Schema Objects](#schemaObject)
+ contained within this OAS document. This MUST be in the form of a URI.
+ """
+
+ servers: List[Server] = [Server(url="/")]
+ """
+ An array of Server Objects, which provide connectivity information to a target
+ server. If the `servers` property is not provided, or is an empty array,
+ the default value would be a [Server Object](#serverObject) with a
+ [url](#serverUrl) value of `/`.
+ """
+
+ paths: Optional[Paths] = None
+ """
+ The available paths and operations for the API.
+ """
+
+ webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None
+ """
+ The incoming webhooks that MAY be received as part of this API and that the API
+ consumer MAY choose to implement.
+ Closely related to the `callbacks` feature, this section describes requests
+ initiated other than by an API call,
+ for example by an out of band registration.
+ The key name is a unique string to refer to each webhook,
+ while the (optionally referenced) Path Item Object describes a request
+ that may be initiated by the API provider and the expected responses.
+ An [example](../examples/v3.1/webhook-example.yaml) is available.
+ """
+
+ components: Optional[Components] = None
+ """
+ An element to hold various schemas for the document.
+ """
+
+ security: Optional[List[SecurityRequirement]] = None
+ """
+ A declaration of which security mechanisms can be used across the API.
+ The list of values includes alternative security requirement objects that can be
+ used. Only one of the security requirement objects need to be satisfied to
+ authorize a request. Individual operations can override this definition.
+ To make security optional, an empty security requirement (`{}`) can be included in
+ the array.
+ """
+
+ tags: Optional[List[Tag]] = None
+ """
+ A list of tags used by the document with additional metadata.
+ The order of the tags can be used to reflect on their order by the parsing tools.
+ Not all tags that are used by the [Operation Object](#operationObject) must be
+ declared. The tags that are not declared MAY be organized randomly or based on the
+ tools' logic. Each tag name in the list MUST be unique.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_1/operation.py b/openapi_pydantic/v3/v3_1/operation.py
new file mode 100644
index 0000000..5b721a7
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/operation.py
@@ -0,0 +1,176 @@
+from typing import Dict, List, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .callback import Callback
+from .external_documentation import ExternalDocumentation
+from .parameter import Parameter
+from .reference import Reference
+from .request_body import RequestBody
+from .responses import Responses
+from .security_requirement import SecurityRequirement
+from .server import Server
+
+_examples = [
+ {
+ "tags": ["pet"],
+ "summary": "Updates a pet in the store with form data",
+ "operationId": "updatePetWithForm",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet that needs to be updated",
+ "required": True,
+ "schema": {"type": "string"},
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "Updated name of the pet",
+ "type": "string",
+ },
+ "status": {
+ "description": "Updated status of the pet",
+ "type": "string",
+ },
+ },
+ "required": ["status"],
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Pet updated.",
+ "content": {"application/json": {}, "application/xml": {}},
+ },
+ "405": {
+ "description": "Method Not Allowed",
+ "content": {"application/json": {}, "application/xml": {}},
+ },
+ },
+ "security": [{"petstore_auth": ["write:pets", "read:pets"]}],
+ }
+]
+
+
+class Operation(BaseModel):
+ """Describes a single API operation on a path."""
+
+ tags: Optional[List[str]] = None
+ """
+ A list of tags for API documentation control.
+ Tags can be used for logical grouping of operations by resources or any other
+ qualifier.
+ """
+
+ summary: Optional[str] = None
+ """
+ A short summary of what the operation does.
+ """
+
+ description: Optional[str] = None
+ """
+ A verbose explanation of the operation behavior.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this operation.
+ """
+
+ operationId: Optional[str] = None
+ """
+ Unique string used to identify the operation.
+ The id MUST be unique among all operations described in the API.
+ The operationId value is **case-sensitive**.
+ Tools and libraries MAY use the operationId to uniquely identify an operation,
+ therefore, it is RECOMMENDED to follow common programming naming conventions.
+ """
+
+ parameters: Optional[List[Union[Parameter, Reference]]] = None
+ """
+ A list of parameters that are applicable for this operation.
+ If a parameter is already defined at the [Path Item](#pathItemParameters),
+ the new definition will override it but can never remove it.
+ The list MUST NOT include duplicated parameters.
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn). The list can use the [Reference Object](#referenceObject)
+ to link to parameters that are defined at the
+ [OpenAPI Object's components/parameters](#componentsParameters).
+ """
+
+ requestBody: Optional[Union[RequestBody, Reference]] = None
+ """
+ The request body applicable for this operation.
+
+ The `requestBody` is fully supported in HTTP methods where the HTTP 1.1
+ specification [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has
+ explicitly defined semantics for request bodies.
+ In other cases where the HTTP spec is vague (such as [GET](https://tools.ietf.org/html/rfc7231#section-4.3.1),
+ [HEAD](https://tools.ietf.org/html/rfc7231#section-4.3.2)
+ and [DELETE](https://tools.ietf.org/html/rfc7231#section-4.3.5)),
+ `requestBody` is permitted but does not have well-defined semantics and SHOULD be
+ avoided if possible.
+ """
+
+ responses: Optional[Responses] = None
+ """
+ The list of possible responses as they are returned from executing this operation.
+ """
+
+ callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None
+ """
+ A map of possible out-of band callbacks related to the parent operation.
+ The key is a unique identifier for the Callback Object.
+ Each value in the map is a [Callback Object](#callbackObject)
+ that describes a request that may be initiated by the API provider and the expected
+ responses.
+ """
+
+ deprecated: bool = False
+ """
+ Declares this operation to be deprecated.
+ Consumers SHOULD refrain from usage of the declared operation.
+ Default value is `false`.
+ """
+
+ security: Optional[List[SecurityRequirement]] = None
+ """
+ A declaration of which security mechanisms can be used for this operation.
+ The list of values includes alternative security requirement objects that can be
+ used. Only one of the security requirement objects need to be satisfied to
+ authorize a request. To make security optional, an empty security requirement
+ (`{}`) can be included in the array. This definition overrides any declared
+ top-level [`security`](#oasSecurity). To remove a top-level security declaration,
+ an empty array can be used.
+ """
+
+ servers: Optional[List[Server]] = None
+ """
+ An alternative `server` array to service this operation.
+ If an alternative `server` object is specified at the Path Item Object or Root
+ level, it will be overridden by this value.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/parameter.py b/openapi_pydantic/v3/v3_1/parameter.py
new file mode 100644
index 0000000..1c7afb5
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/parameter.py
@@ -0,0 +1,235 @@
+import enum
+from typing import Any, Dict, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .example import Example
+from .media_type import MediaType
+from .reference import Reference
+from .schema import Schema
+
+_examples = [
+ {
+ "name": "token",
+ "in": "header",
+ "description": "token to be passed as a header",
+ "required": True,
+ "schema": {
+ "type": "array",
+ "items": {"type": "integer", "format": "int64"},
+ },
+ "style": "simple",
+ },
+ {
+ "name": "username",
+ "in": "path",
+ "description": "username to fetch",
+ "required": True,
+ "schema": {"type": "string"},
+ },
+ {
+ "name": "id",
+ "in": "query",
+ "description": "ID of the object to fetch",
+ "required": False,
+ "schema": {"type": "array", "items": {"type": "string"}},
+ "style": "form",
+ "explode": True,
+ },
+ {
+ "in": "query",
+ "name": "freeForm",
+ "schema": {
+ "type": "object",
+ "additionalProperties": {"type": "integer"},
+ },
+ "style": "form",
+ },
+ {
+ "in": "query",
+ "name": "coordinates",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["lat", "long"],
+ "properties": {
+ "lat": {"type": "number"},
+ "long": {"type": "number"},
+ },
+ }
+ }
+ },
+ },
+]
+
+
+class ParameterLocation(str, enum.Enum):
+ """The location of a given parameter."""
+
+ QUERY = "query"
+ HEADER = "header"
+ PATH = "path"
+ COOKIE = "cookie"
+
+
+class ParameterBase(BaseModel):
+ """
+ Base class for Parameter and Header.
+
+ (Header is like Parameter, but has no `name` or `in` fields.)
+ """
+
+ description: Optional[str] = None
+ """
+ A brief description of the parameter.
+ This could contain examples of use.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ required: bool = False
+ """
+ Determines whether this parameter is mandatory.
+ If the [parameter location](#parameterIn) is `"path"`, this property is
+ **REQUIRED** and its value MUST be `true`.
+ Otherwise, the property MAY be included and its default value is `false`.
+ """
+
+ deprecated: bool = False
+ """
+ Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
+ Default value is `false`.
+ """
+
+ style: Optional[str] = None
+ """
+ Describes how the parameter value will be serialized depending on the type of the
+ parameter value. Default values (based on value of `in`):
+
+ - for `query` - `form`;
+ - for `path` - `simple`;
+ - for `header` - `simple`;
+ - for `cookie` - `form`.
+ """
+
+ 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.
+ For other types of parameters this property has no effect.
+ When [`style`](#parameterStyle) is `form`, the default value is `true`.
+ For all other styles, the default value is `false`.
+ """
+
+ param_schema: Optional[Union[Schema, Reference]] = Field(
+ default=None, alias="schema"
+ )
+ """
+ The schema defining the type used for the parameter.
+ """
+
+ example: Optional[Any] = None
+ """
+ Example of the parameter's potential value.
+ The example SHOULD match the specified schema and encoding properties if present.
+ The `example` field is mutually exclusive of the `examples` field.
+ Furthermore, if referencing a `schema` that contains an example,
+ the `example` value SHALL _override_ the example provided by the schema.
+ To represent examples of media types that cannot naturally be represented in JSON
+ or YAML, a string value can contain the example with escaping where necessary.
+ """
+
+ examples: Optional[Dict[str, Union[Example, Reference]]] = None
+ """
+ Examples of the parameter's potential value.
+ Each example SHOULD contain a value in the correct format as specified in the
+ parameter encoding. The `examples` field is mutually exclusive of the `example`
+ field.
+ Furthermore, if referencing a `schema` that contains an example,
+ the `examples` value SHALL _override_ the example provided by the schema.
+ """
+
+ """
+ For more complex scenarios, the [`content`](#parameterContent) property
+ can define the media type and schema of the parameter.
+ A parameter MUST contain either a `schema` property, or a `content` property, but
+ not both.
+ When `example` or `examples` are provided in conjunction with the `schema` object,
+ the example MUST follow the prescribed serialization strategy for the parameter.
+ """
+
+ content: Optional[Dict[str, MediaType]] = None
+ """
+ A map containing the representations for the parameter.
+ The key is the media type and the value describes it.
+ The map MUST only contain one entry.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
+
+
+class Parameter(ParameterBase):
+ """
+ Describes a single operation parameter.
+
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn).
+ """
+
+ """Fixed Fields"""
+
+ name: str
+ """
+ **REQUIRED**. The name of the parameter.
+ Parameter names are *case sensitive*.
+
+ - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a
+ template expression occurring within the [path](#pathsPath) field in the
+ [Paths Object](#pathsObject).
+ See [Path Templating](#pathTemplating) for further information.
+ - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`,
+ `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored.
+ - For all other cases, the `name` corresponds to the parameter name used by the
+ [`in`](#parameterIn) property.
+ """
+
+ param_in: ParameterLocation = Field(alias="in")
+ """
+ **REQUIRED**. The location of the parameter. Possible values are `"query"`,
+ `"header"`, `"path"` or `"cookie"`.
+ """
+
+ allowEmptyValue: bool = False
+ """
+ Sets the ability to pass empty-valued parameters.
+ This is valid only for `query` parameters and allows sending a parameter with an
+ empty value. Default value is `false`.
+ If [`style`](#parameterStyle) is used, and if behavior is `n/a` (cannot be
+ serialized), the value of `allowEmptyValue` SHALL be ignored.
+ Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later
+ revision.
+ """
+
+ allowReserved: bool = False
+ """
+ Determines whether the parameter value SHOULD allow reserved characters,
+ as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
+ `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
+ This property only applies to parameters with an `in` value of `query`.
+ The default value is `false`.
+ """
diff --git a/openapi_pydantic/v3/v3_1/path_item.py b/openapi_pydantic/v3/v3_1/path_item.py
new file mode 100644
index 0000000..aa5b1d6
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/path_item.py
@@ -0,0 +1,153 @@
+from typing import List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .operation import Operation
+from .parameter import Parameter
+from .reference import Reference
+from .server import Server
+
+_examples = [
+ {
+ "get": {
+ "description": "Returns pets based on ID",
+ "summary": "Find pets by ID",
+ "operationId": "getPetsById",
+ "responses": {
+ "200": {
+ "description": "pet response",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Pet"},
+ }
+ }
+ },
+ },
+ "default": {
+ "description": "error payload",
+ "content": {
+ "text/html": {
+ "schema": {"$ref": "#/components/schemas/ErrorModel"}
+ }
+ },
+ },
+ },
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "ID of pet to use",
+ "required": True,
+ "schema": {"type": "array", "items": {"type": "string"}},
+ "style": "simple",
+ }
+ ],
+ }
+]
+
+
+class PathItem(BaseModel):
+ """
+ Describes the operations available on a single path.
+ A Path Item MAY be empty, due to [ACL constraints](#securityFiltering).
+ The path itself is still exposed to the documentation viewer
+ but they will not know which operations and parameters are available.
+ """
+
+ ref: Optional[str] = Field(default=None, alias="$ref")
+ """
+ Allows for an external definition of this path item.
+ The referenced structure MUST be in the format of a
+ [Path Item Object](#pathItemObject).
+
+ In case a Path Item Object field appears both in the defined object and the
+ referenced object, the behavior is undefined.
+ See the rules for resolving [Relative References](#relativeReferencesURI).
+ """
+
+ summary: Optional[str] = None
+ """
+ An optional, string summary, intended to apply to all operations in this path.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional, string description, intended to apply to all operations in this path.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ get: Optional[Operation] = None
+ """
+ A definition of a GET operation on this path.
+ """
+
+ put: Optional[Operation] = None
+ """
+ A definition of a PUT operation on this path.
+ """
+
+ post: Optional[Operation] = None
+ """
+ A definition of a POST operation on this path.
+ """
+
+ delete: Optional[Operation] = None
+ """
+ A definition of a DELETE operation on this path.
+ """
+
+ options: Optional[Operation] = None
+ """
+ A definition of a OPTIONS operation on this path.
+ """
+
+ head: Optional[Operation] = None
+ """
+ A definition of a HEAD operation on this path.
+ """
+
+ patch: Optional[Operation] = None
+ """
+ A definition of a PATCH operation on this path.
+ """
+
+ trace: Optional[Operation] = None
+ """
+ A definition of a TRACE operation on this path.
+ """
+
+ servers: Optional[List[Server]] = None
+ """
+ An alternative `server` array to service all operations in this path.
+ """
+
+ parameters: Optional[List[Union[Parameter, Reference]]] = None
+ """
+ A list of parameters that are applicable for all the operations described under
+ this path. These parameters can be overridden at the operation level, but cannot be
+ removed there. The list MUST NOT include duplicated parameters.
+ A unique parameter is defined by a combination of a [name](#parameterName) and
+ [location](#parameterIn). The list can use the [Reference Object](#referenceObject)
+ to link to parameters that are defined at the
+ [OpenAPI Object's components/parameters](#componentsParameters).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/paths.py b/openapi_pydantic/v3/v3_1/paths.py
new file mode 100644
index 0000000..8e2bbd9
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/paths.py
@@ -0,0 +1,28 @@
+from typing import Dict
+
+from .path_item import PathItem
+
+Paths = Dict[str, PathItem]
+"""
+Holds the relative paths to the individual endpoints and their operations.
+The path is appended to the URL from the [`Server Object`](#serverObject) in order to
+construct the full URL.
+
+The Paths MAY be empty, due to
+[Access Control List (ACL) constraints](#securityFiltering).
+"""
+
+"""Patterned Fields"""
+
+# "/{path}" : PathItem
+"""
+A relative path to an individual endpoint.
+The field name MUST begin with a forward slash (`/`).
+The path is **appended** (no relative URL resolution) to the expanded URL
+from the [`Server Object`](#serverObject)'s `url` field in order to construct the full
+URL. [Path templating](#pathTemplating) is allowed.
+When matching URLs, concrete (non-templated) paths would be matched before their
+templated counterparts. Templated paths with the same hierarchy but different templated
+names MUST NOT exist as they are identical. In case of ambiguous matching, it's up to
+the tooling to decide which one to use.
+"""
diff --git a/openapi_pydantic/v3/v3_1/reference.py b/openapi_pydantic/v3/v3_1/reference.py
new file mode 100644
index 0000000..9c75cab
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/reference.py
@@ -0,0 +1,55 @@
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {"$ref": "#/components/schemas/Pet"},
+ {"$ref": "Pet.json"},
+ {"$ref": "definitions.json#/Pet"},
+]
+
+
+class Reference(BaseModel):
+ """
+ A simple object to allow referencing other components in the OpenAPI document.
+
+ The `$ref` string value contains a URI [RFC3986](https://tools.ietf.org/html/rfc3986),
+ which identifies the location of the value being referenced.
+
+ See the rules for resolving [Relative References](#relativeReferencesURI).
+ """
+
+ ref: str = Field(alias="$ref")
+ """**REQUIRED**. The reference identifier. This MUST be in the form of a URI."""
+
+ summary: Optional[str] = None
+ """
+ A short summary which by default SHOULD override that of the referenced component.
+ If the referenced object-type does not allow a `summary` field, then this field has
+ no effect.
+ """
+
+ description: Optional[str] = None
+ """
+ A description which by default SHOULD override that of the referenced component.
+ [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.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/request_body.py b/openapi_pydantic/v3/v3_1/request_body.py
new file mode 100644
index 0000000..1741a5a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/request_body.py
@@ -0,0 +1,95 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .media_type import MediaType
+
+_examples = [
+ {
+ "description": "user to add to the system",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"},
+ "examples": {
+ "user": {
+ "summary": "User Example",
+ "externalValue": "http://foo.bar/examples/user-example.json",
+ }
+ },
+ },
+ "application/xml": {
+ "schema": {"$ref": "#/components/schemas/User"},
+ "examples": {
+ "user": {
+ "summary": "User example in XML",
+ "externalValue": "http://foo.bar/examples/user-example.xml",
+ }
+ },
+ },
+ "text/plain": {
+ "examples": {
+ "user": {
+ "summary": "User example in Plain text",
+ "externalValue": "http://foo.bar/examples/user-example.txt",
+ }
+ }
+ },
+ "*/*": {
+ "examples": {
+ "user": {
+ "summary": "User example in other format",
+ "externalValue": "http://foo.bar/examples/user-example.whatever",
+ }
+ }
+ },
+ },
+ },
+ {
+ "description": "user to add to the system",
+ "content": {
+ "text/plain": {"schema": {"type": "array", "items": {"type": "string"}}}
+ },
+ },
+]
+
+
+class RequestBody(BaseModel):
+ """Describes a single request body."""
+
+ description: Optional[str] = None
+ """
+ A brief description of the request body.
+ This could contain examples of use.
+
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ content: Dict[str, MediaType]
+ """
+ **REQUIRED**. The content of the request body.
+ The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D)
+ and the value describes it.
+
+ For requests that match multiple keys, only the most specific key is applicable.
+ e.g. text/plain overrides text/*
+ """
+
+ required: bool = False
+ """
+ Determines if the request body is required in the request. Defaults to `false`.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/response.py b/openapi_pydantic/v3/v3_1/response.py
new file mode 100644
index 0000000..fa20933
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/response.py
@@ -0,0 +1,100 @@
+from typing import Dict, Optional, Union
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .header import Header
+from .link import Link
+from .media_type import MediaType
+from .reference import Reference
+
+_examples = [
+ {
+ "description": "A complex object array response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/VeryComplexType"},
+ }
+ }
+ },
+ },
+ {
+ "description": "A simple string response",
+ "content": {"text/plain": {"schema": {"type": "string"}}},
+ },
+ {
+ "description": "A simple string response",
+ "content": {"text/plain": {"schema": {"type": "string", "example": "whoa!"}}},
+ "headers": {
+ "X-Rate-Limit-Limit": {
+ "description": "The number of allowed requests in the "
+ "current period",
+ "schema": {"type": "integer"},
+ },
+ "X-Rate-Limit-Remaining": {
+ "description": "The number of remaining requests in the "
+ "current period",
+ "schema": {"type": "integer"},
+ },
+ "X-Rate-Limit-Reset": {
+ "description": "The number of seconds left in the current period",
+ "schema": {"type": "integer"},
+ },
+ },
+ },
+ {"description": "object created"},
+]
+
+
+class Response(BaseModel):
+ """
+ Describes a single response from an API Operation, including design-time,
+ static `links` to operations based on the response.
+ """
+
+ description: str
+ """
+ **REQUIRED**. A short description of the response.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ headers: Optional[Dict[str, Union[Header, Reference]]] = None
+ """
+ Maps a header name to its definition.
+ [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) states header names are case
+ insensitive.
+ If a response header is defined with the name `"Content-Type"`, it SHALL be ignored.
+ """
+
+ content: Optional[Dict[str, MediaType]] = None
+ """
+ A map containing descriptions of potential response payloads.
+ The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D)
+ and the value describes it.
+
+ For responses that match multiple keys, only the most specific key is applicable.
+ e.g. text/plain overrides text/*
+ """
+
+ links: Optional[Dict[str, Union[Link, Reference]]] = None
+ """
+ A map of operations links that can be followed from the response.
+ The key of the map is a short name for the link, following the naming constraints
+ of the names for [Component Objects](#componentsObject).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/responses.py b/openapi_pydantic/v3/v3_1/responses.py
new file mode 100644
index 0000000..5c9a78a
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/responses.py
@@ -0,0 +1,52 @@
+from typing import Dict, Union
+
+from .reference import Reference
+from .response import Response
+
+Responses = Dict[str, Union[Response, Reference]]
+"""
+A container for the expected responses of an operation.
+The container maps a HTTP response code to the expected response.
+
+The documentation is not necessarily expected to cover all possible HTTP response codes
+because they may not be known in advance.
+However, documentation is expected to cover a successful operation response and any
+known errors.
+
+The `default` MAY be used as a default response object for all HTTP codes
+that are not covered individually by the specification.
+
+The `Responses Object` MUST contain at least one response code, and it
+SHOULD be the response for a successful operation call.
+"""
+
+"""Fixed Fields"""
+
+# default: Optional[Union[Response, Reference]]
+"""
+The documentation of responses other than the ones declared for specific HTTP response
+codes.
+Use this field to cover undeclared responses.
+A [Reference Object](#referenceObject) can link to a response
+that the [OpenAPI Object's components/responses](#componentsResponses) section defines.
+"""
+
+"""Patterned Fields"""
+# {httpStatusCode}: Optional[Union[Response, Reference]]
+"""
+Any [HTTP status code](#httpCodes) can be used as the property name,
+but only one property per code, to describe the expected response for that HTTP status
+code.
+
+A [Reference Object](#referenceObject) can link to a response
+that is defined in the [OpenAPI Object's components/responses](#componentsResponses)
+section.
+This field MUST be enclosed in quotation marks (for example, "200") for compatibility
+between JSON and YAML.
+To define a range of response codes, this field MAY contain the uppercase wildcard
+character `X`.
+For example, `2XX` represents all response codes between `[200-299]`.
+Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`.
+If a response is defined using an explicit code,
+the explicit code definition takes precedence over the range definition for that code.
+"""
diff --git a/openapi_pydantic/v3/v3_1/schema.py b/openapi_pydantic/v3/v3_1/schema.py
new file mode 100644
index 0000000..0d53e30
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/schema.py
@@ -0,0 +1,971 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra, min_length_arg
+
+from .datatype import DataType
+from .discriminator import Discriminator
+from .external_documentation import ExternalDocumentation
+from .reference import Reference
+from .xml import XML
+
+_examples = [
+ {"type": "string", "format": "email"},
+ {
+ "type": "object",
+ "required": ["name"],
+ "properties": {
+ "name": {"type": "string"},
+ "address": {"$ref": "#/components/schemas/Address"},
+ "age": {"type": "integer", "format": "int32", "minimum": 0},
+ },
+ },
+ {"type": "object", "additionalProperties": {"type": "string"}},
+ {
+ "type": "object",
+ "additionalProperties": {"$ref": "#/components/schemas/ComplexModel"},
+ },
+ {
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "format": "int64"},
+ "name": {"type": "string"},
+ },
+ "required": ["name"],
+ "example": {"name": "Puma", "id": 1},
+ },
+ {
+ "type": "object",
+ "required": ["message", "code"],
+ "properties": {
+ "message": {"type": "string"},
+ "code": {"type": "integer", "minimum": 100, "maximum": 600},
+ },
+ },
+ {
+ "allOf": [
+ {"$ref": "#/components/schemas/ErrorModel"},
+ {
+ "type": "object",
+ "required": ["rootCause"],
+ "properties": {"rootCause": {"type": "string"}},
+ },
+ ]
+ },
+ {
+ "type": "object",
+ "discriminator": {"propertyName": "petType"},
+ "properties": {
+ "name": {"type": "string"},
+ "petType": {"type": "string"},
+ },
+ "required": ["name", "petType"],
+ },
+ {
+ "description": "A representation of a cat. "
+ "Note that `Cat` will be used as the discriminator value.",
+ "allOf": [
+ {"$ref": "#/components/schemas/Pet"},
+ {
+ "type": "object",
+ "properties": {
+ "huntingSkill": {
+ "type": "string",
+ "description": "The measured skill for hunting",
+ "default": "lazy",
+ "enum": [
+ "clueless",
+ "lazy",
+ "adventurous",
+ "aggressive",
+ ],
+ }
+ },
+ "required": ["huntingSkill"],
+ },
+ ],
+ },
+ {
+ "description": "A representation of a dog. "
+ "Note that `Dog` will be used as the discriminator value.",
+ "allOf": [
+ {"$ref": "#/components/schemas/Pet"},
+ {
+ "type": "object",
+ "properties": {
+ "packSize": {
+ "type": "integer",
+ "format": "int32",
+ "description": "the size of the pack the dog is from",
+ "default": 0,
+ "minimum": 0,
+ }
+ },
+ "required": ["packSize"],
+ },
+ ],
+ },
+]
+
+
+class Schema(BaseModel):
+ """
+ The Schema Object allows the definition of input and output data types.
+ These types can be objects, but also primitives and arrays.
+ This object is a superset of
+ the [JSON Schema Specification Draft 2020-12](https://tools.ietf.org/html/draft-bhutton-json-schema-00).
+
+ For more information about the properties,
+ see [JSON Schema Core](https://tools.ietf.org/html/draft-wright-json-schema-00)
+ and [JSON Schema Validation](https://tools.ietf.org/html/draft-wright-json-schema-validation-00).
+
+ Unless stated otherwise, the property definitions follow those of JSON Schema
+ and do not add any additional semantics.
+ Where JSON Schema indicates that behavior is defined by the application (e.g. for
+ annotations), OAS also defers the definition of semantics to the application
+ consuming the OpenAPI document.
+ """
+
+ """
+ The following properties are taken directly from the
+ [JSON Schema Core](https://tools.ietf.org/html/draft-wright-json-schema-00)
+ and follow the same specifications:
+ """
+
+ allOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ This keyword's value MUST be a non-empty array. Each item of the
+ array MUST be a valid JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against all schemas defined by this keyword's
+ value.
+ """
+
+ anyOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ This keyword's value MUST be a non-empty array. Each item of the
+ array MUST be a valid JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against at least one schema defined by this
+ keyword's value. Note that when annotations are being collected, all
+ subschemas MUST be examined so that annotations are collected from
+ each subschema that validates successfully.
+ """
+
+ oneOf: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ This keyword's value MUST be a non-empty array. Each item of the
+ array MUST be a valid JSON Schema.
+
+ An instance validates successfully against this keyword if it
+ validates successfully against exactly one schema defined by this
+ keyword's value.
+ """
+
+ schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not")
+ """
+ This keyword's value MUST be a valid JSON Schema.
+
+ An instance is valid against this keyword if it fails to validate
+ successfully against the schema defined by this keyword.
+ """
+
+ schema_if: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="if")
+ """
+ This keyword's value MUST be a valid JSON Schema.
+
+ This validation outcome of this keyword's subschema has no direct
+ effect on the overall validation result. Rather, it controls which
+ of the "then" or "else" keywords are evaluated.
+
+ Instances that successfully validate against this keyword's subschema
+ MUST also be valid against the subschema value of the "then" keyword,
+ if present.
+
+ Instances that fail to validate against this keyword's subschema MUST
+ also be valid against the subschema value of the "else" keyword, if
+ present.
+
+ If annotations (Section 7.7) are being collected, they are collected
+ from this keyword's subschema in the usual way, including when the
+ keyword is present without either "then" or "else".
+ """
+
+ then: Optional[Union[Reference, "Schema"]] = None
+ """
+ This keyword's value MUST be a valid JSON Schema.
+
+ When "if" is present, and the instance successfully validates against
+ its subschema, then validation succeeds against this keyword if the
+ instance also successfully validates against this keyword's
+ subschema.
+
+ This keyword has no effect when "if" is absent, or when the instance
+ fails to validate against its subschema. Implementations MUST NOT
+ evaluate the instance against this keyword, for either validation or
+ annotation collection purposes, in such cases.
+ """
+
+ schema_else: Optional[Union[Reference, "Schema"]] = Field(
+ default=None, alias="else"
+ )
+ """
+ This keyword's value MUST be a valid JSON Schema.
+
+ When "if" is present, and the instance fails to validate against its
+ subschema, then validation succeeds against this keyword if the
+ instance successfully validates against this keyword's subschema.
+
+ This keyword has no effect when "if" is absent, or when the instance
+ successfully validates against its subschema. Implementations MUST
+ NOT evaluate the instance against this keyword, for either validation
+ or annotation collection purposes, in such cases.
+ """
+
+ dependentSchemas: Optional[Dict[str, Union[Reference, "Schema"]]] = None
+ """
+ This keyword specifies subschemas that are evaluated if the instance
+ is an object and contains a certain property.
+
+ This keyword's value MUST be an object. Each value in the object
+ MUST be a valid JSON Schema.
+
+ If the object key is a property in the instance, the entire instance
+ must validate against the subschema. Its use is dependent on the
+ presence of the property.
+
+ Omitting this keyword has the same behavior as an empty object.
+ """
+
+ prefixItems: Optional[List[Union[Reference, "Schema"]]] = None
+ """
+ The value of "prefixItems" MUST be a non-empty array of valid JSON
+ Schemas.
+
+ Validation succeeds if each element of the instance validates against
+ the schema at the same position, if any. This keyword does not
+ constrain the length of the array. If the array is longer than this
+ keyword's value, this keyword validates only the prefix of matching
+ length.
+
+ This keyword produces an annotation value which is the largest index
+ to which this keyword applied a subschema. The value MAY be a
+ boolean true if a subschema was applied to every index of the
+ instance, such as is produced by the "items" keyword. This
+ annotation affects the behavior of "items" and "unevaluatedItems".
+
+ Omitting this keyword has the same assertion behavior as an empty
+ array.
+ """
+
+ items: Optional[Union[Reference, "Schema"]] = None
+ """
+ The value of "items" MUST be a valid JSON Schema.
+
+ This keyword applies its subschema to all instance elements at
+ indexes greater than the length of the "prefixItems" array in the
+ same schema object, as reported by the annotation result of that
+ "prefixItems" keyword. If no such annotation result exists, "items"
+ applies its subschema to all instance array elements. [[CREF11: Note
+ that the behavior of "items" without "prefixItems" is identical to
+ that of the schema form of "items" in prior drafts. When
+ "prefixItems" is present, the behavior of "items" is identical to the
+ former "additionalItems" keyword. ]]
+
+ If the "items" subschema is applied to any positions within the
+ instance array, it produces an annotation result of boolean true,
+ indicating that all remaining array elements have been evaluated
+ against this keyword's subschema.
+
+ Omitting this keyword has the same assertion behavior as an empty
+ schema.
+
+ Implementations MAY choose to implement or optimize this keyword in
+ another way that produces the same effect, such as by directly
+ checking for the presence and size of a "prefixItems" array.
+ Implementations that do not support annotation collection MUST do so.
+ """
+
+ contains: Optional[Union[Reference, "Schema"]] = None
+ """
+ The value of this keyword MUST be a valid JSON Schema.
+
+ An array instance is valid against "contains" if at least one of its
+ elements is valid against the given schema. The subschema MUST be
+ applied to every array element even after the first match has been
+ found, in order to collect annotations for use by other keywords.
+ This is to ensure that all possible annotations are collected.
+
+ Logically, the validation result of applying the value subschema to
+ each item in the array MUST be ORed with "false", resulting in an
+ overall validation result.
+
+ This keyword produces an annotation value which is an array of the
+ indexes to which this keyword validates successfully when applying
+ its subschema, in ascending order. The value MAY be a boolean "true"
+ if the subschema validates successfully when applied to every index
+ of the instance. The annotation MUST be present if the instance
+ array to which this keyword's schema applies is empty.
+ """
+
+ properties: Optional[Dict[str, Union[Reference, "Schema"]]] = None
+ """
+ The value of "properties" MUST be an object. Each value of this
+ object MUST be a valid JSON Schema.
+
+ Validation succeeds if, for each name that appears in both the
+ instance and as a name within this keyword's value, the child
+ instance for that name successfully validates against the
+ corresponding schema.
+
+ The annotation result of this keyword is the set of instance property
+ names matched by this keyword.
+
+ Omitting this keyword has the same assertion behavior as an empty
+ object.
+ """
+
+ patternProperties: Optional[Dict[str, Union[Reference, "Schema"]]] = None
+ """
+ The value of "patternProperties" MUST be an object. Each property
+ name of this object SHOULD be a valid regular expression, according
+ to the ECMA-262 regular expression dialect. Each property value of
+ this object MUST be a valid JSON Schema.
+
+ Validation succeeds if, for each instance name that matches any
+ regular expressions that appear as a property name in this keyword's
+ value, the child instance for that name successfully validates
+ against each schema that corresponds to a matching regular
+ expression.
+
+ The annotation result of this keyword is the set of instance property
+ names matched by this keyword.
+
+ Omitting this keyword has the same assertion behavior as an empty
+ object.
+ """
+
+ additionalProperties: Optional[Union[Reference, "Schema", bool]] = None
+ """
+ The value of "additionalProperties" MUST be a valid JSON Schema.
+
+ The behavior of this keyword depends on the presence and annotation
+ results of "properties" and "patternProperties" within the same
+ schema object. Validation with "additionalProperties" applies only
+ to the child values of instance names that do not appear in the
+ annotation results of either "properties" or "patternProperties".
+
+ For all such properties, validation succeeds if the child instance
+ validates against the "additionalProperties" schema.
+
+ The annotation result of this keyword is the set of instance property
+ names validated by this keyword's subschema.
+
+ Omitting this keyword has the same assertion behavior as an empty
+ schema.
+
+ Implementations MAY choose to implement or optimize this keyword in
+ another way that produces the same effect, such as by directly
+ checking the names in "properties" and the patterns in
+ "patternProperties" against the instance property set.
+ Implementations that do not support annotation collection MUST do so.
+ """
+
+ propertyNames: Optional[Union[Reference, "Schema"]] = None
+ """
+ The value of "propertyNames" MUST be a valid JSON Schema.
+
+ If the instance is an object, this keyword validates if every
+ property name in the instance validates against the provided schema.
+ Note the property name that the schema is testing will always be a
+ string.
+
+ Omitting this keyword has the same behavior as an empty schema.
+ """
+
+ unevaluatedItems: Optional[Union[Reference, "Schema"]] = None
+ """
+ The value of "unevaluatedItems" MUST be a valid JSON Schema.
+
+ The behavior of this keyword depends on the annotation results of
+ adjacent keywords that apply to the instance location being
+ validated. Specifically, the annotations from "prefixItems",
+ "items", and "contains", which can come from those keywords when they
+ are adjacent to the "unevaluatedItems" keyword. Those three
+ annotations, as well as "unevaluatedItems", can also result from any
+ and all adjacent in-place applicator (Section 10.2) keywords. This
+ includes but is not limited to the in-place applicators defined in
+ this document.
+
+ If no relevant annotations are present, the "unevaluatedItems"
+ subschema MUST be applied to all locations in the array. If a
+ boolean true value is present from any of the relevant annotations,
+ "unevaluatedItems" MUST be ignored. Otherwise, the subschema MUST be
+ applied to any index greater than the largest annotation value for
+ "prefixItems", which does not appear in any annotation value for
+ "contains".
+
+ This means that "prefixItems", "items", "contains", and all in-place
+ applicators MUST be evaluated before this keyword can be evaluated.
+ Authors of extension keywords MUST NOT define an in-place applicator
+ that would need to be evaluated after this keyword.
+
+ If the "unevaluatedItems" subschema is applied to any positions
+ within the instance array, it produces an annotation result of
+ boolean true, analogous to the behavior of "items".
+
+ Omitting this keyword has the same assertion behavior as an empty
+ schema.
+ """
+
+ unevaluatedProperties: Optional[Union[Reference, "Schema"]] = None
+ """
+ The value of "unevaluatedProperties" MUST be a valid JSON Schema.
+
+ The behavior of this keyword depends on the annotation results of
+ adjacent keywords that apply to the instance location being
+ validated. Specifically, the annotations from "properties",
+ "patternProperties", and "additionalProperties", which can come from
+ those keywords when they are adjacent to the "unevaluatedProperties"
+ keyword. Those three annotations, as well as
+ "unevaluatedProperties", can also result from any and all adjacent
+ in-place applicator (Section 10.2) keywords. This includes but is
+ not limited to the in-place applicators defined in this document.
+
+ Validation with "unevaluatedProperties" applies only to the child
+ values of instance names that do not appear in the "properties",
+ "patternProperties", "additionalProperties", or
+ "unevaluatedProperties" annotation results that apply to the instance
+ location being validated.
+
+ For all such properties, validation succeeds if the child instance
+ validates against the "unevaluatedProperties" schema.
+
+ This means that "properties", "patternProperties",
+ "additionalProperties", and all in-place applicators MUST be
+ evaluated before this keyword can be evaluated. Authors of extension
+ keywords MUST NOT define an in-place applicator that would need to be
+ evaluated after this keyword.
+
+ The annotation result of this keyword is the set of instance property
+ names validated by this keyword's subschema.
+
+ Omitting this keyword has the same assertion behavior as an empty
+ schema.
+ """
+
+ """
+ The following properties are taken directly from the
+ [JSON Schema Validation](https://tools.ietf.org/html/draft-wright-json-schema-validation-00)
+ and follow the same specifications:
+ """
+
+ type: Optional[Union[DataType, List[DataType]]] = None
+ """
+ The value of this keyword MUST be either a string or an array. If it
+ is an array, elements of the array MUST be strings and MUST be
+ unique.
+
+ String values MUST be one of the six primitive types ("null",
+ "boolean", "object", "array", "number", or "string"), or "integer"
+ which matches any number with a zero fractional part.
+
+ An instance validates if and only if the instance is in any of the
+ sets listed for this keyword.
+ """
+
+ enum: Optional[List[Any]] = Field(default=None, **min_length_arg(1))
+ """
+ The value of this keyword MUST be an array. This array SHOULD have
+ at least one element. Elements in the array SHOULD be unique.
+
+ An instance validates successfully against this keyword if its value
+ is equal to one of the elements in this keyword's array value.
+
+ Elements in the array might be of any type, including null.
+ """
+
+ const: Optional[Any] = None
+ """
+ The value of this keyword MAY be of any type, including null.
+
+ Use of this keyword is functionally equivalent to an "enum"
+ (Section 6.1.2) with a single value.
+
+ An instance validates successfully against this keyword if its value
+ is equal to the value of the keyword.
+ """
+
+ multipleOf: Optional[float] = Field(default=None, gt=0.0)
+ """
+ The value of "multipleOf" MUST be a number, strictly greater than 0.
+
+ A numeric instance is only valid if division by this keyword's value
+ results in an integer.
+ """
+
+ maximum: Optional[float] = None
+ """
+ The value of "maximum" MUST be a number, representing an inclusive
+ upper limit for a numeric instance.
+
+ If the instance is a number, then this keyword validates only if the
+ instance is less than or exactly equal to "maximum".
+ """
+
+ exclusiveMaximum: Optional[float] = None
+ """
+ The value of "exclusiveMaximum" MUST be a number, representing an
+ exclusive upper limit for a numeric instance.
+
+ If the instance is a number, then the instance is valid only if it
+ has a value strictly less than (not equal to) "exclusiveMaximum".
+ """
+
+ minimum: Optional[float] = None
+ """
+ The value of "minimum" MUST be a number, representing an inclusive
+ lower limit for a numeric instance.
+
+ If the instance is a number, then this keyword validates only if the
+ instance is greater than or exactly equal to "minimum".
+ """
+
+ exclusiveMinimum: Optional[float] = None
+ """
+ The value of "exclusiveMinimum" MUST be a number, representing an
+ exclusive lower limit for a numeric instance.
+
+ If the instance is a number, then the instance is valid only if it
+ has a value strictly greater than (not equal to) "exclusiveMinimum".
+ """
+
+ maxLength: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ A string instance is valid against this keyword if its length is less
+ than, or equal to, the value of this keyword.
+
+ The length of a string instance is defined as the number of its
+ characters as defined by RFC 8259 [RFC8259].
+ """
+
+ minLength: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ A string instance is valid against this keyword if its length is
+ greater than, or equal to, the value of this keyword.
+
+ The length of a string instance is defined as the number of its
+ characters as defined by RFC 8259 [RFC8259].
+
+ Omitting this keyword has the same behavior as a value of 0.
+ """
+
+ pattern: Optional[str] = None
+ """
+ The value of this keyword MUST be a string. This string SHOULD be a
+ valid regular expression, according to the ECMA-262 regular
+ expression dialect.
+
+ A string instance is considered valid if the regular expression
+ matches the instance successfully. Recall: regular expressions are
+ not implicitly anchored.
+ """
+
+ maxItems: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ An array instance is valid against "maxItems" if its size is less
+ than, or equal to, the value of this keyword.
+ """
+
+ minItems: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ An array instance is valid against "minItems" if its size is greater
+ than, or equal to, the value of this keyword.
+
+ Omitting this keyword has the same behavior as a value of 0.
+ """
+
+ uniqueItems: Optional[bool] = None
+ """
+ The value of this keyword MUST be a boolean.
+
+ If this keyword has boolean value false, the instance validates
+ successfully. If it has boolean value true, the instance validates
+ successfully if all of its elements are unique.
+
+ Omitting this keyword has the same behavior as a value of false.
+ """
+
+ maxContains: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ If "contains" is not present within the same schema object, then this
+ keyword has no effect.
+
+ An instance array is valid against "maxContains" in two ways,
+ depending on the form of the annotation result of an adjacent
+ "contains" [json-schema] keyword. The first way is if the annotation
+ result is an array and the length of that array is less than or equal
+ to the "maxContains" value. The second way is if the annotation
+ result is a boolean "true" and the instance array length is less than
+ or equal to the "maxContains" value.
+ """
+
+ minContains: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ If "contains" is not present within the same schema object, then this
+ keyword has no effect.
+
+ An instance array is valid against "minContains" in two ways,
+ depending on the form of the annotation result of an adjacent
+ "contains" [json-schema] keyword. The first way is if the annotation
+ result is an array and the length of that array is greater than or
+ equal to the "minContains" value. The second way is if the
+ annotation result is a boolean "true" and the instance array length
+ is greater than or equal to the "minContains" value.
+
+ A value of 0 is allowed, but is only useful for setting a range of
+ occurrences from 0 to the value of "maxContains". A value of 0 with
+ no "maxContains" causes "contains" to always pass validation.
+
+ Omitting this keyword has the same behavior as a value of 1.
+ """
+
+ maxProperties: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ An object instance is valid against "maxProperties" if its number of
+ properties is less than, or equal to, the value of this keyword.
+ """
+
+ minProperties: Optional[int] = Field(default=None, ge=0)
+ """
+ The value of this keyword MUST be a non-negative integer.
+
+ An object instance is valid against "minProperties" if its number of
+ properties is greater than, or equal to, the value of this keyword.
+
+ Omitting this keyword has the same behavior as a value of 0.
+ """
+
+ required: Optional[List[str]] = None
+ """
+ The value of this keyword MUST be an array. Elements of this array,
+ if any, MUST be strings, and MUST be unique.
+
+ An object instance is valid against this keyword if every item in the
+ array is the name of a property in the instance.
+
+ Omitting this keyword has the same behavior as an empty array.
+ """
+
+ dependentRequired: Optional[Dict[str, List[str]]] = None
+ """
+ The value of this keyword MUST be an object. Properties in this
+ object, if any, MUST be arrays. Elements in each array, if any, MUST
+ be strings, and MUST be unique.
+
+ This keyword specifies properties that are required if a specific
+ other property is present. Their requirement is dependent on the
+ presence of the other property.
+
+ Validation succeeds if, for each name that appears in both the
+ instance and as a name within this keyword's value, every item in the
+ corresponding array is also the name of a property in the instance.
+
+ Omitting this keyword has the same behavior as an empty object.
+ """
+
+ schema_format: Optional[str] = Field(default=None, alias="format")
+ """
+ From OpenAPI:
+ See [Data Type Formats](#dataTypeFormat) for further details.
+ While relying on JSON Schema's defined formats, the OAS offers a few additional
+ predefined formats.
+
+ From JSON Schema:
+ Structural validation alone may be insufficient to allow an
+ application to correctly utilize certain values. The "format"
+ annotation keyword is defined to allow schema authors to convey
+ semantic information for a fixed subset of values which are
+ accurately described by authoritative resources, be they RFCs or
+ other external specifications.
+
+ The value of this keyword is called a format attribute. It MUST be a
+ string. A format attribute can generally only validate a given set
+ of instance types. If the type of the instance to validate is not in
+ this set, validation for this format attribute and instance SHOULD
+ succeed. All format attributes defined in this section apply to
+ strings, but a format attribute can be specified to apply to any
+ instance types defined in the data model defined in the core JSON
+ Schema. [json-schema] [[CREF1: Note that the "type" keyword in this
+ specification defines an "integer" type which is not part of the data
+ model. Therefore a format attribute can be limited to numbers, but
+ not specifically to integers. However, a numeric format can be used
+ alongside the "type" keyword with a value of "integer", or could be
+ explicitly defined to always pass if the number is not an integer,
+ which produces essentially the same behavior as only applying to
+ integers. ]]
+ """
+
+ contentEncoding: Optional[str] = None
+ """
+ If the instance value is a string, this property defines that the
+ string SHOULD be interpreted as binary data and decoded using the
+ encoding named by this property.
+
+ Possible values indicating base 16, 32, and 64 encodings with several
+ variations are listed in RFC 4648 [RFC4648]. Additionally, sections
+ 6.7 and 6.8 of RFC 2045 [RFC2045] provide encodings used in MIME. As
+ "base64" is defined in both RFCs, the definition from RFC 4648 SHOULD
+ be assumed unless the string is specifically intended for use in a
+ MIME context. Note that all of these encodings result in strings
+ consisting only of 7-bit ASCII characters. Therefore, this keyword
+ has no meaning for strings containing characters outside of that
+ range.
+
+ If this keyword is absent, but "contentMediaType" is present, this
+ indicates that the encoding is the identity encoding, meaning that no
+ transformation was needed in order to represent the content in a
+ UTF-8 string.
+ """
+
+ contentMediaType: Optional[str] = None
+ """
+ If the instance is a string, this property indicates the media type
+ of the contents of the string. If "contentEncoding" is present, this
+ property describes the decoded string.
+
+ The value of this property MUST be a string, which MUST be a media
+ type, as defined by RFC 2046 [RFC2046].
+ """
+
+ contentSchema: Optional[Union[Reference, "Schema"]] = None
+ """
+ If the instance is a string, and if "contentMediaType" is present,
+ this property contains a schema which describes the structure of the
+ string.
+
+ This keyword MAY be used with any media type that can be mapped into
+ JSON Schema's data model.
+
+ The value of this property MUST be a valid JSON schema. It SHOULD be
+ ignored if "contentMediaType" is not present.
+ """
+
+ title: Optional[str] = None
+ """
+ The value of "title" MUST be a string.
+
+ The title can be used to decorate a user interface with
+ information about the data produced by this user interface.
+ A title will preferably be short.
+ """
+
+ description: Optional[str] = None
+ """
+ From OpenAPI:
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+
+ From JSON Schema:
+ The value "description" MUST be a string.
+
+ The description can be used to decorate a user interface with
+ information about the data produced by this user interface.
+ A description will provide explanation about the purpose of
+ the instance described by this schema.
+ """
+
+ default: Optional[Any] = None
+ """
+ There are no restrictions placed on the value of this keyword. When
+ multiple occurrences of this keyword are applicable to a single sub-
+ instance, implementations SHOULD remove duplicates.
+
+ This keyword can be used to supply a default JSON value associated
+ with a particular schema. It is RECOMMENDED that a default value be
+ valid against the associated schema.
+ """
+
+ deprecated: Optional[bool] = None
+ """
+ The value of this keyword MUST be a boolean. When multiple
+ occurrences of this keyword are applicable to a single sub-instance,
+ applications SHOULD consider the instance location to be deprecated
+ if any occurrence specifies a true value.
+
+ If "deprecated" has a value of boolean true, it indicates that
+ applications SHOULD refrain from usage of the declared property. It
+ MAY mean the property is going to be removed in the future.
+
+ A root schema containing "deprecated" with a value of true indicates
+ that the entire resource being described MAY be removed in the
+ future.
+
+ The "deprecated" keyword applies to each instance location to which
+ the schema object containing the keyword successfully applies. This
+ can result in scenarios where every array item or object property is
+ deprecated even though the containing array or object is not.
+
+ Omitting this keyword has the same behavior as a value of false.
+ """
+
+ readOnly: Optional[bool] = None
+ """
+ The value of "readOnly" MUST be a boolean. When multiple
+ occurrences of this keyword are applicable to a single sub-instance,
+ the resulting behavior SHOULD be as for a true value if any
+ occurrence specifies a true value, and SHOULD be as for a false value
+ otherwise.
+
+ If "readOnly" has a value of boolean true, it indicates that the
+ value of the instance is managed exclusively by the owning authority,
+ and attempts by an application to modify the value of this property
+ are expected to be ignored or rejected by that owning authority.
+
+ An instance document that is marked as "readOnly" for the entire
+ document MAY be ignored if sent to the owning authority, or MAY
+ result in an error, at the authority's discretion.
+
+ For example, "readOnly" would be used to mark a database-generated
+ serial number as read-only, while "writeOnly" would be used to mark a
+ password input field.
+
+ This keyword can be used to assist in user interface instance
+ generation. In particular, an application MAY choose to use a widget
+ that hides input values as they are typed for write-only fields.
+
+ Omitting these keywords has the same behavior as values of false.
+ """
+
+ writeOnly: Optional[bool] = None
+ """
+ The value of "writeOnly" MUST be a boolean. When multiple
+ occurrences of this keyword are applicable to a single sub-instance,
+ the resulting behavior SHOULD be as for a true value if any
+ occurrence specifies a true value, and SHOULD be as for a false value
+ otherwise.
+
+ If "writeOnly" has a value of boolean true, it indicates that the
+ value is never present when the instance is retrieved from the owning
+ authority. It can be present when sent to the owning authority to
+ update or create the document (or the resource it represents), but it
+ will not be included in any updated or newly created version of the
+ instance.
+
+ An instance document that is marked as "writeOnly" for the entire
+ document MAY be returned as a blank document of some sort, or MAY
+ produce an error upon retrieval, or have the retrieval request
+ ignored, at the authority's discretion.
+
+ For example, "readOnly" would be used to mark a database-generated
+ serial number as read-only, while "writeOnly" would be used to mark a
+ password input field.
+
+ This keyword can be used to assist in user interface instance
+ generation. In particular, an application MAY choose to use a widget
+ that hides input values as they are typed for write-only fields.
+
+ Omitting these keywords has the same behavior as values of false.
+ """
+
+ examples: Optional[List[Any]] = None
+ """
+ The value of this keyword MUST be an array. There are no
+ restrictions placed on the values within the array. When multiple
+ occurrences of this keyword are applicable to a single sub-instance,
+ implementations MUST provide a flat array of all values rather than
+ an array of arrays.
+
+ This keyword can be used to provide sample JSON values associated
+ with a particular schema, for the purpose of illustrating usage. It
+ is RECOMMENDED that these values be valid against the associated
+ schema.
+
+ Implementations MAY use the value(s) of "default", if present, as an
+ additional example. If "examples" is absent, "default" MAY still be
+ used in this manner.
+ """
+
+ """
+ The OpenAPI Specification's base vocabulary is comprised of the following keywords:
+ """
+
+ discriminator: Optional[Discriminator] = None
+ """
+ Adds support for polymorphism.
+ The discriminator is an object name that is used to differentiate between other
+ schemas which may satisfy the payload description.
+ See [Composition and Inheritance](#schemaComposition) for more details.
+ """
+
+ xml: Optional[XML] = None
+ """
+ This MAY be used only on properties schemas.
+ It has no effect on root schemas.
+ Adds additional metadata to describe the XML representation of this property.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this schema.
+ """
+
+ example: Optional[Any] = None
+ """
+ A free-form property to include an example of an instance for this schema.
+ To represent examples that cannot be naturally represented in JSON or YAML,
+ a string value can be used to contain the example with escaping where necessary.
+
+ Deprecated: The example property has been deprecated in favor of the JSON Schema
+ examples keyword.
+ Use of example is discouraged, and later versions of this specification may remove
+ it.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
+
+
+if TYPE_CHECKING:
+
+ def schema_validate(
+ obj: Any,
+ *,
+ strict: Optional[bool] = None,
+ from_attributes: Optional[bool] = None,
+ context: Optional[Dict[str, Any]] = None
+ ) -> Schema: ...
+
+elif PYDANTIC_V2:
+ schema_validate = Schema.model_validate
+
+else:
+ schema_validate = Schema.parse_obj
diff --git a/openapi_pydantic/v3/v3_1/security_requirement.py b/openapi_pydantic/v3/v3_1/security_requirement.py
new file mode 100644
index 0000000..f8e2733
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/security_requirement.py
@@ -0,0 +1,33 @@
+from typing import Dict, List
+
+SecurityRequirement = Dict[str, List[str]]
+"""
+Lists the required security schemes to execute this operation.
+The name used for each property MUST correspond to a security scheme declared in the
+[Security Schemes](#componentsSecuritySchemes) under the
+[Components Object](#componentsObject).
+
+Security Requirement Objects that contain multiple schemes require that
+all schemes MUST be satisfied for a request to be authorized.
+This enables support for scenarios where multiple query parameters or HTTP headers
+are required to convey security information.
+
+When a list of Security Requirement Objects is defined on the
+[OpenAPI Object](#oasObject) or [Operation Object](#operationObject),
+only one of the Security Requirement Objects in the list needs to be satisfied to
+authorize the request.
+"""
+
+"""Patterned Fields"""
+
+# {name}: List[str]
+"""
+Each name MUST correspond to a security scheme which is declared
+in the [Security Schemes](#componentsSecuritySchemes) under the
+[Components Object](#componentsObject).
+If the security scheme is of type `"oauth2"` or `"openIdConnect"`,
+then the value is a list of scope names required for the execution,
+and the list MAY be empty if authorization does not require a specified scope.
+For other security scheme types, the array MAY contain a list of role names which are
+required for the execution, but are not otherwise defined or exchanged in-band.
+"""
diff --git a/openapi_pydantic/v3/v3_1/security_scheme.py b/openapi_pydantic/v3/v3_1/security_scheme.py
new file mode 100644
index 0000000..88a447c
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/security_scheme.py
@@ -0,0 +1,119 @@
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .oauth_flows import OAuthFlows
+
+_examples = [
+ {"type": "http", "scheme": "basic"},
+ {"type": "apiKey", "name": "api_key", "in": "header"},
+ {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
+ {
+ "type": "oauth2",
+ "flows": {
+ "implicit": {
+ "authorizationUrl": "https://example.com/api/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets",
+ },
+ }
+ },
+ },
+ {
+ "type": "openIdConnect",
+ "openIdConnectUrl": "https://example.com/openIdConnect",
+ },
+ {
+ "type": "openIdConnect",
+ "openIdConnectUrl": "openIdConnect",
+ }, # issue #5: allow relative path
+]
+
+
+class SecurityScheme(BaseModel):
+ """
+ Defines a security scheme that can be used by the operations.
+
+ Supported schemes are HTTP authentication,
+ an API key (either as a header, a cookie parameter or as a query parameter),
+ mutual TLS (use of a client certificate),
+ OAuth2's common flows (implicit, password, client credentials and authorization
+ code) as defined in [RFC6749](https://tools.ietf.org/html/rfc6749),
+ and [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06).
+
+ Please note that as of 2020, the implicit flow is about to be deprecated by
+ [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics).
+ Recommended for most use case is Authorization Code Grant flow with PKCE.
+ """
+
+ type: str
+ """
+ **REQUIRED**. The type of the security scheme.
+ Valid values are `"apiKey"`, `"http"`, "mutualTLS", `"oauth2"`, `"openIdConnect"`.
+ """
+
+ description: Optional[str] = None
+ """
+ A description for security scheme.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ name: Optional[str] = None
+ """
+ **REQUIRED** for `apiKey`. The name of the header, query or cookie parameter to be
+ used.
+ """
+
+ security_scheme_in: Optional[str] = Field(alias="in", default=None)
+ """
+ **REQUIRED** for `apiKey`. The location of the API key. Valid values are `"query"`,
+ `"header"` or `"cookie"`.
+ """
+
+ scheme: Optional[str] = None
+ """
+ **REQUIRED** for `http`. The name of the HTTP Authorization scheme to be used in the
+ [Authorization header as defined in RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1).
+
+ The values used SHOULD be registered in the
+ [IANA Authentication Scheme registry](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml).
+ """
+
+ bearerFormat: Optional[str] = None
+ """
+ A hint to the client to identify how the bearer token is formatted.
+
+ Bearer tokens are usually generated by an authorization server,
+ so this information is primarily for documentation purposes.
+ """
+
+ flows: Optional[OAuthFlows] = None
+ """
+ **REQUIRED** for `oauth2`. An object containing configuration information for the
+ flow types supported.
+ """
+
+ openIdConnectUrl: Optional[str] = None
+ """
+ **REQUIRED** for `openIdConnect`. OpenId Connect URL to discover OAuth2
+ configuration values. This MUST be in the form of a URL. The OpenID Connect
+ standard requires the use of TLS.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ populate_by_name=True,
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ allow_population_by_field_name = True
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/server.py b/openapi_pydantic/v3/v3_1/server.py
new file mode 100644
index 0000000..0b98b75
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/server.py
@@ -0,0 +1,67 @@
+from typing import Dict, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .server_variable import ServerVariable
+
+_examples = [
+ {
+ "url": "https://development.gigantic-server.com/v1",
+ "description": "Development server",
+ },
+ {
+ "url": "https://{username}.gigantic-server.com:{port}/{basePath}",
+ "description": "The production API server",
+ "variables": {
+ "username": {
+ "default": "demo",
+ "description": "this value is assigned by the service "
+ "provider, in this example `gigantic-server.com`",
+ },
+ "port": {"enum": ["8443", "443"], "default": "8443"},
+ "basePath": {"default": "v2"},
+ },
+ },
+]
+
+
+class Server(BaseModel):
+ """An object representing a Server."""
+
+ url: str
+ """
+ **REQUIRED**. A URL to the target host.
+
+ This URL supports Server Variables and MAY be relative,
+ to indicate that the host location is relative to the location where the OpenAPI
+ document is being served.
+ Variable substitutions will be made when a variable is named in `{`brackets`}`.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional string describing the host designated by the URL.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ variables: Optional[Dict[str, ServerVariable]] = None
+ """
+ A map between a variable name and its value.
+
+ The value is used for substitution in the server's URL template.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/server_variable.py b/openapi_pydantic/v3/v3_1/server_variable.py
new file mode 100644
index 0000000..3de5734
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/server_variable.py
@@ -0,0 +1,42 @@
+from typing import List, Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+
+class ServerVariable(BaseModel):
+ """An object representing a Server Variable for server URL template substitution."""
+
+ enum: Optional[List[str]] = None
+ """
+ An enumeration of string values to be used if the substitution options are from a
+ limited set. The array SHOULD NOT be empty.
+ """
+
+ default: str
+ """
+ **REQUIRED**. The default value to use for substitution,
+ which SHALL be sent if an alternate value is _not_ supplied.
+ Note this behavior is different than the [Schema Object's](#schemaObject) treatment
+ of default values, because in those cases parameter values are optional.
+ If the [`enum`](#serverVariableEnum) is defined, the value MUST exist in the enum's
+ values.
+ """
+
+ description: Optional[str] = None
+ """
+ An optional description for the server variable.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
diff --git a/openapi_pydantic/v3/v3_1/tag.py b/openapi_pydantic/v3/v3_1/tag.py
new file mode 100644
index 0000000..9ec0d03
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/tag.py
@@ -0,0 +1,47 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+from .external_documentation import ExternalDocumentation
+
+_examples = [{"name": "pet", "description": "Pets operations"}]
+
+
+class Tag(BaseModel):
+ """
+ Adds metadata to a single tag that is used by the
+ [Operation Object](#operationObject).
+ It is not mandatory to have a Tag Object per tag defined in the Operation Object
+ instances.
+ """
+
+ name: str
+ """
+ **REQUIRED**. The name of the tag.
+ """
+
+ description: Optional[str] = None
+ """
+ A short description for the tag.
+ [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text
+ representation.
+ """
+
+ externalDocs: Optional[ExternalDocumentation] = None
+ """
+ Additional external documentation for this tag.
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/openapi_pydantic/v3/v3_1/xml.py b/openapi_pydantic/v3/v3_1/xml.py
new file mode 100644
index 0000000..4972fca
--- /dev/null
+++ b/openapi_pydantic/v3/v3_1/xml.py
@@ -0,0 +1,72 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
+
+_examples = [
+ {"name": "animal"},
+ {"attribute": True},
+ {"wrapped": True},
+ {"namespace": "http://example.com/schema/sample", "prefix": "sample"},
+ {"name": "aliens", "wrapped": True},
+]
+
+
+class XML(BaseModel):
+ """
+ A metadata object that allows for more fine-tuned XML model definitions.
+
+ When using arrays, XML element names are *not* inferred (for singular/plural forms)
+ and the `name` property SHOULD be used to add that information.
+ See examples for expected behavior.
+ """
+
+ name: Optional[str] = None
+ """
+ Replaces the name of the element/attribute used for the described schema property.
+ When defined within `items`, it will affect the name of the individual XML elements
+ within the list.
+ When defined alongside `type` being `array` (outside the `items`),
+ it will affect the wrapping element and only if `wrapped` is `true`.
+ If `wrapped` is `false`, it will be ignored.
+ """
+
+ namespace: Optional[str] = None
+ """
+ The URI of the namespace definition.
+ Value MUST be in the form of an absolute URI.
+ """
+
+ prefix: Optional[str] = None
+ """
+ The prefix to be used for the [name](#xmlName).
+ """
+
+ attribute: bool = False
+ """
+ Declares whether the property definition translates to an attribute instead of an
+ element. Default value is `false`.
+ """
+
+ wrapped: bool = False
+ """
+ MAY be used only for an array definition.
+ Signifies whether the array is wrapped
+ (for example, ``) or unwrapped (``).
+ Default value is `false`.
+ The definition takes effect only when defined alongside `type` being `array`
+ (outside the `items`).
+ """
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="allow",
+ json_schema_extra={"examples": _examples},
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.allow
+ schema_extra = {"examples": _examples}
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..6f37c80
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,1250 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
+
+[[package]]
+name = "attrs"
+version = "24.2.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
+ {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
+tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "black"
+version = "24.8.0"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
+ {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
+ {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
+ {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
+ {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
+ {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
+ {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
+ {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
+ {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
+ {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
+ {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
+ {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
+ {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
+ {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
+ {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
+ {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
+ {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
+ {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
+ {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
+ {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
+ {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
+ {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "certifi"
+version = "2024.8.30"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
+ {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
+]
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+description = "Validate configuration and produce human readable error messages."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.0"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"},
+ {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"},
+ {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"},
+ {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"},
+ {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"},
+ {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"},
+ {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"},
+ {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"},
+ {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"},
+ {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "coverage"
+version = "7.6.1"
+description = "Code coverage measurement for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"},
+ {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"},
+ {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"},
+ {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"},
+ {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"},
+ {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"},
+ {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"},
+ {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"},
+ {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"},
+ {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"},
+ {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"},
+ {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"},
+ {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"},
+ {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"},
+ {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"},
+ {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"},
+ {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"},
+ {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"},
+ {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"},
+ {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"},
+ {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"},
+ {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"},
+ {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"},
+ {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"},
+ {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"},
+ {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"},
+ {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"},
+ {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"},
+ {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"},
+ {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"},
+ {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"},
+ {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"},
+ {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"},
+ {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"},
+ {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"},
+ {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"},
+ {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"},
+ {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"},
+ {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"},
+ {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"},
+ {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"},
+ {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"},
+ {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"},
+ {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"},
+ {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"},
+ {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"},
+ {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"},
+ {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"},
+ {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"},
+ {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"},
+ {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"},
+ {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"},
+ {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"},
+ {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"},
+ {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"},
+ {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"},
+ {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"},
+ {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"},
+ {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"},
+ {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"},
+ {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"},
+ {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"},
+ {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"},
+ {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"},
+ {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"},
+ {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"},
+ {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"},
+ {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"},
+ {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"},
+ {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"},
+ {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"},
+ {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"},
+]
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "distlib"
+version = "0.3.9"
+description = "Distribution utilities"
+optional = false
+python-versions = "*"
+files = [
+ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
+ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.2.2"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
+ {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "filelock"
+version = "3.16.1"
+description = "A platform independent file lock."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
+ {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
+typing = ["typing-extensions (>=4.12.2)"]
+
+[[package]]
+name = "identify"
+version = "2.6.1"
+description = "File identification library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
+ {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
+]
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+]
+
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "importlib-resources"
+version = "6.4.5"
+description = "Read resources from Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"},
+ {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"},
+]
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.23.0"
+description = "An implementation of JSON Schema validation for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"},
+ {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+jsonschema-specifications = ">=2023.03.6"
+pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""}
+referencing = ">=0.28.4"
+rpds-py = ">=0.7.1"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
+
+[[package]]
+name = "jsonschema-path"
+version = "0.3.3"
+description = "JSONSchema Spec with object-oriented paths"
+optional = false
+python-versions = "<4.0.0,>=3.8.0"
+files = [
+ {file = "jsonschema_path-0.3.3-py3-none-any.whl", hash = "sha256:203aff257f8038cd3c67be614fe6b2001043408cb1b4e36576bc4921e09d83c4"},
+ {file = "jsonschema_path-0.3.3.tar.gz", hash = "sha256:f02e5481a4288ec062f8e68c808569e427d905bedfecb7f2e4c69ef77957c382"},
+]
+
+[package.dependencies]
+pathable = ">=0.4.1,<0.5.0"
+PyYAML = ">=5.1"
+referencing = ">=0.28.0,<0.36.0"
+requests = ">=2.31.0,<3.0.0"
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2023.12.1"
+description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"},
+ {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"},
+]
+
+[package.dependencies]
+importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+referencing = ">=0.31.0"
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.10.0"
+description = "A fast and thorough lazy object proxy."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"},
+ {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"},
+ {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"},
+ {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"},
+ {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"},
+ {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"},
+ {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.13.0"
+description = "Optional static typing for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
+ {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
+ {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
+ {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
+ {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
+ {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
+ {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
+ {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
+ {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
+ {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
+ {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
+ {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
+ {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
+ {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
+ {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
+ {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
+ {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
+ {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
+ {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
+ {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
+ {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
+ {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
+ {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
+ {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
+ {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
+ {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
+ {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=1.0.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=4.6.0"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+faster-cache = ["orjson"]
+install-types = ["pip"]
+mypyc = ["setuptools (>=50)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+description = "Node.js virtual environment builder"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
+ {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
+]
+
+[[package]]
+name = "openapi-schema-validator"
+version = "0.6.2"
+description = "OpenAPI schema validation for Python"
+optional = false
+python-versions = ">=3.8.0,<4.0.0"
+files = [
+ {file = "openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8"},
+ {file = "openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804"},
+]
+
+[package.dependencies]
+jsonschema = ">=4.19.1,<5.0.0"
+jsonschema-specifications = ">=2023.5.2,<2024.0.0"
+rfc3339-validator = "*"
+
+[[package]]
+name = "openapi-spec-validator"
+version = "0.7.1"
+description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator"
+optional = false
+python-versions = ">=3.8.0,<4.0.0"
+files = [
+ {file = "openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959"},
+ {file = "openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7"},
+]
+
+[package.dependencies]
+importlib-resources = {version = ">=5.8,<7.0", markers = "python_version < \"3.9\""}
+jsonschema = ">=4.18.0,<5.0.0"
+jsonschema-path = ">=0.3.1,<0.4.0"
+lazy-object-proxy = ">=1.7.1,<2.0.0"
+openapi-schema-validator = ">=0.6.0,<0.7.0"
+
+[[package]]
+name = "packaging"
+version = "24.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
+ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
+]
+
+[[package]]
+name = "pathable"
+version = "0.4.3"
+description = "Object-oriented paths"
+optional = false
+python-versions = ">=3.7.0,<4.0.0"
+files = [
+ {file = "pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14"},
+ {file = "pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab"},
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "pkgutil-resolve-name"
+version = "1.3.10"
+description = "Resolve a name to an object."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"},
+ {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.6"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
+ {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.11.2)"]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pre-commit"
+version = "2.21.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"},
+ {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"},
+]
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+virtualenv = ">=20.10.0"
+
+[[package]]
+name = "pydantic"
+version = "2.10.2"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
+ {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.27.1"
+typing-extensions = ">=4.12.2"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.27.1"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
+ {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
+ {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
+ {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
+ {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
+ {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
+ {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
+ {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
+ {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
+ {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
+ {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
+ {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
+ {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
+ {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
+ {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
+ {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
+ {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "pytest"
+version = "8.3.3"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
+ {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=1.5,<2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "5.0.0"
+description = "Pytest plugin for measuring coverage."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
+ {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
+]
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
+ {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
+ {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
+ {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
+ {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
+ {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
+ {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
+ {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
+ {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
+ {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
+ {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
+ {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
+ {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
+ {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
+ {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
+ {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
+ {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
+ {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
+ {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
+ {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
+ {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
+ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
+ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+]
+
+[[package]]
+name = "referencing"
+version = "0.35.1"
+description = "JSON Referencing + Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"},
+ {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+rpds-py = ">=0.7.0"
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
+ {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+description = "A pure python RFC3339 validator"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"},
+ {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"},
+]
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+name = "rpds-py"
+version = "0.20.1"
+description = "Python bindings to Rust's persistent data structures (rpds)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"},
+ {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86"},
+ {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd"},
+ {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e"},
+ {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356"},
+ {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899"},
+ {file = "rpds_py-0.20.1-cp310-none-win32.whl", hash = "sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff"},
+ {file = "rpds_py-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711"},
+ {file = "rpds_py-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75"},
+ {file = "rpds_py-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0"},
+ {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e"},
+ {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8"},
+ {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4"},
+ {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3"},
+ {file = "rpds_py-0.20.1-cp311-none-win32.whl", hash = "sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732"},
+ {file = "rpds_py-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84"},
+ {file = "rpds_py-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17"},
+ {file = "rpds_py-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb"},
+ {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa"},
+ {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc"},
+ {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd"},
+ {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5"},
+ {file = "rpds_py-0.20.1-cp312-none-win32.whl", hash = "sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c"},
+ {file = "rpds_py-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb"},
+ {file = "rpds_py-0.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e"},
+ {file = "rpds_py-0.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496"},
+ {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4"},
+ {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7"},
+ {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a"},
+ {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb"},
+ {file = "rpds_py-0.20.1-cp313-none-win32.whl", hash = "sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782"},
+ {file = "rpds_py-0.20.1-cp313-none-win_amd64.whl", hash = "sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e"},
+ {file = "rpds_py-0.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191"},
+ {file = "rpds_py-0.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9"},
+ {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad"},
+ {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28"},
+ {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1"},
+ {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc"},
+ {file = "rpds_py-0.20.1-cp38-none-win32.whl", hash = "sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1"},
+ {file = "rpds_py-0.20.1-cp38-none-win_amd64.whl", hash = "sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425"},
+ {file = "rpds_py-0.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad"},
+ {file = "rpds_py-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f"},
+ {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f"},
+ {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f"},
+ {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1"},
+ {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf"},
+ {file = "rpds_py-0.20.1-cp39-none-win32.whl", hash = "sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca"},
+ {file = "rpds_py-0.20.1-cp39-none-win_amd64.whl", hash = "sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d"},
+ {file = "rpds_py-0.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684"},
+ {file = "rpds_py-0.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a"},
+ {file = "rpds_py-0.20.1.tar.gz", hash = "sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350"},
+]
+
+[[package]]
+name = "ruff"
+version = "0.7.2"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
+ {file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
+ {file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
+ {file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
+ {file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
+ {file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
+ {file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
+ {file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
+ {file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
+ {file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
+ {file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
+ {file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
+]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "tomli"
+version = "2.0.2"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
+ {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
+ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.2.3"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
+ {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "virtualenv"
+version = "20.27.1"
+description = "Virtual Python Environment builder"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"},
+ {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"},
+]
+
+[package.dependencies]
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+
+[[package]]
+name = "zipp"
+version = "3.20.2"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"},
+ {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
+type = ["pytest-mypy"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.8"
+content-hash = "834d62056bc88495edf2a6475aaef19829eda09adbc9ac377f9ba9c6a65c872d"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..5c06584
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,57 @@
+[tool.poetry]
+name = "openapi-pydantic"
+version = "0.5.1"
+description = "Pydantic OpenAPI schema implementation"
+authors = ["Mike Oakley "]
+readme = "README.md"
+repository = "https://github.com/mike-oakley/openapi-pydantic"
+license = "MIT"
+keywords = [
+ "openapi",
+ "schema",
+ "parser",
+ "pydantic",
+ "validation",
+]
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Framework :: Pydantic",
+]
+include = ["openapi_pydantic/py.typed"]
+
+[tool.poetry.urls]
+changelog = "https://github.com/mike-oakley/openapi-pydantic/releases"
+
+[tool.poetry.dependencies]
+python = "^3.8"
+pydantic = ">=1.8"
+
+[tool.poetry.group.test.dependencies]
+pytest = "^8.2.2"
+pytest-cov = "^5.0.0"
+openapi-spec-validator = "^0.7.0"
+
+[tool.poetry.group.dev.dependencies]
+black = "^24.4.2"
+mypy = "^1.8.0"
+pre-commit = "^2.16.0"
+ruff = "^0.7.2"
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.mypy]
+warn_unused_ignores = true
+warn_redundant_casts = true
+warn_unused_configs = true
+warn_unreachable = true
+warn_return_any = true
+strict = true
+disallow_any_generics = false
+implicit_reexport = false
+show_error_codes = true
+files = ["openapi_pydantic/", "tests/"]
+plugins = ["pydantic.mypy"]
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/swagger_openapi_v3.0.1.json b/tests/data/swagger_openapi_v3.0.1.json
new file mode 100644
index 0000000..5047064
--- /dev/null
+++ b/tests/data/swagger_openapi_v3.0.1.json
@@ -0,0 +1,1103 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "Swagger Petstore",
+ "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
+ "termsOfService": "http://swagger.io/terms/",
+ "contact": {
+ "email": "apiteam@swagger.io"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "1.0.0"
+ },
+ "externalDocs": {
+ "description": "Find out more about Swagger",
+ "url": "http://swagger.io"
+ },
+ "servers": [
+ {
+ "url": "https://petstore.swagger.io/v2"
+ },
+ {
+ "url": "http://petstore.swagger.io/v2"
+ }
+ ],
+ "tags": [
+ {
+ "name": "pet",
+ "description": "Everything about your Pets",
+ "externalDocs": {
+ "description": "Find out more",
+ "url": "http://swagger.io"
+ }
+ },
+ {
+ "name": "store",
+ "description": "Access to Petstore orders"
+ },
+ {
+ "name": "user",
+ "description": "Operations about user",
+ "externalDocs": {
+ "description": "Find out more about our store",
+ "url": "http://swagger.io"
+ }
+ }
+ ],
+ "paths": {
+ "/pet": {
+ "put": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Update an existing pet",
+ "operationId": "updatePet",
+ "requestBody": {
+ "description": "Pet object that needs to be added to the store",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "Pet not found",
+ "content": {}
+ },
+ "405": {
+ "description": "Validation exception",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ],
+ "x-codegen-request-body-name": "body"
+ },
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Add a new pet to the store",
+ "operationId": "addPet",
+ "requestBody": {
+ "description": "Pet object that needs to be added to the store",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "405": {
+ "description": "Invalid input",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ],
+ "x-codegen-request-body-name": "body"
+ }
+ },
+ "/pet/findByStatus": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Finds Pets by status",
+ "description": "Multiple status values can be provided with comma separated strings",
+ "operationId": "findPetsByStatus",
+ "parameters": [
+ {
+ "name": "status",
+ "in": "query",
+ "description": "Status values that need to be considered for filter",
+ "required": true,
+ "style": "form",
+ "explode": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": "available",
+ "enum": [
+ "available",
+ "pending",
+ "sold"
+ ]
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid status value",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/findByTags": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Finds Pets by tags",
+ "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
+ "operationId": "findPetsByTags",
+ "parameters": [
+ {
+ "name": "tags",
+ "in": "query",
+ "description": "Tags to filter by",
+ "required": true,
+ "style": "form",
+ "explode": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ },
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid tag value",
+ "content": {}
+ }
+ },
+ "deprecated": true,
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/{petId}": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Find pet by ID",
+ "description": "Returns a single pet",
+ "operationId": "getPetById",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet to return",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ },
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid ID supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "Pet not found",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Updates a pet in the store with form data",
+ "operationId": "updatePetWithForm",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet that needs to be updated",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Updated name of the pet"
+ },
+ "status": {
+ "type": "string",
+ "description": "Updated status of the pet"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "405": {
+ "description": "Invalid input",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ },
+ "delete": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Deletes a pet",
+ "operationId": "deletePet",
+ "parameters": [
+ {
+ "name": "api_key",
+ "in": "header",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "Pet id to delete",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "Pet not found",
+ "content": {}
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/{petId}/uploadImage": {
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "uploads an image",
+ "operationId": "uploadFile",
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet to update",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "properties": {
+ "additionalMetadata": {
+ "type": "string",
+ "description": "Additional data to pass to server"
+ },
+ "file": {
+ "type": "string",
+ "description": "file to upload",
+ "format": "binary"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiResponse"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/store/inventory": {
+ "get": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Returns pet inventories by status",
+ "description": "Returns a map of status codes to quantities",
+ "operationId": "getInventory",
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "api_key": []
+ }
+ ]
+ }
+ },
+ "/store/order": {
+ "post": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Place an order for a pet",
+ "operationId": "placeOrder",
+ "requestBody": {
+ "description": "order placed for purchasing the pet",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/Order"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Order"
+ }
+ },
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Order"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid Order",
+ "content": {}
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ }
+ },
+ "/store/order/{orderId}": {
+ "get": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Find purchase order by ID",
+ "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
+ "operationId": "getOrderById",
+ "parameters": [
+ {
+ "name": "orderId",
+ "in": "path",
+ "description": "ID of pet that needs to be fetched",
+ "required": true,
+ "schema": {
+ "maximum": 10,
+ "minimum": 1,
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/Order"
+ }
+ },
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Order"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid ID supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "Order not found",
+ "content": {}
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Delete purchase order by ID",
+ "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
+ "operationId": "deleteOrder",
+ "parameters": [
+ {
+ "name": "orderId",
+ "in": "path",
+ "description": "ID of the order that needs to be deleted",
+ "required": true,
+ "schema": {
+ "minimum": 1,
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "Order not found",
+ "content": {}
+ }
+ }
+ }
+ },
+ "/user": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Create user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "createUser",
+ "requestBody": {
+ "description": "Created user object",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "default": {
+ "description": "successful operation",
+ "content": {}
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ }
+ },
+ "/user/createWithArray": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates list of users with given input array",
+ "operationId": "createUsersWithArrayInput",
+ "requestBody": {
+ "description": "List of user object",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "default": {
+ "description": "successful operation",
+ "content": {}
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ }
+ },
+ "/user/createWithList": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates list of users with given input array",
+ "operationId": "createUsersWithListInput",
+ "requestBody": {
+ "description": "List of user object",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "default": {
+ "description": "successful operation",
+ "content": {}
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ }
+ },
+ "/user/login": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Logs user into the system",
+ "operationId": "loginUser",
+ "parameters": [
+ {
+ "name": "username",
+ "in": "query",
+ "description": "The user name for login",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "password",
+ "in": "query",
+ "description": "The password for login in clear text",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "headers": {
+ "X-Rate-Limit": {
+ "description": "calls per hour allowed by the user",
+ "schema": {
+ "type": "integer",
+ "format": "int32"
+ }
+ },
+ "X-Expires-After": {
+ "description": "date in UTC when token expires",
+ "schema": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "content": {
+ "application/xml": {
+ "schema": {
+ "type": "string"
+ }
+ },
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid username/password supplied",
+ "content": {}
+ }
+ }
+ }
+ },
+ "/user/logout": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Logs out current logged in user session",
+ "operationId": "logoutUser",
+ "responses": {
+ "default": {
+ "description": "successful operation",
+ "content": {}
+ }
+ }
+ }
+ },
+ "/user/{username}": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Get user by user name",
+ "operationId": "getUserByName",
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "The name that needs to be fetched. Use user1 for testing. ",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "content": {
+ "application/xml": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ },
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid username supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "User not found",
+ "content": {}
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Updated user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "updateUser",
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "name that need to be updated",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Updated user object",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "400": {
+ "description": "Invalid user supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "User not found",
+ "content": {}
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ },
+ "delete": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Delete user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "deleteUser",
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "The name that needs to be deleted",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid username supplied",
+ "content": {}
+ },
+ "404": {
+ "description": "User not found",
+ "content": {}
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Order": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "petId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "quantity": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "shipDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "status": {
+ "type": "string",
+ "description": "Order Status",
+ "enum": [
+ "placed",
+ "approved",
+ "delivered"
+ ]
+ },
+ "complete": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "xml": {
+ "name": "Order"
+ }
+ },
+ "Category": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Category"
+ }
+ },
+ "User": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "username": {
+ "type": "string"
+ },
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ },
+ "userStatus": {
+ "type": "integer",
+ "description": "User Status",
+ "format": "int32"
+ }
+ },
+ "xml": {
+ "name": "User"
+ }
+ },
+ "Tag": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Tag"
+ }
+ },
+ "Pet": {
+ "required": [
+ "name",
+ "photoUrls"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "category": {
+ "$ref": "#/components/schemas/Category"
+ },
+ "name": {
+ "type": "string",
+ "example": "doggie"
+ },
+ "photoUrls": {
+ "type": "array",
+ "xml": {
+ "name": "photoUrl",
+ "wrapped": true
+ },
+ "items": {
+ "type": "string"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "xml": {
+ "name": "tag",
+ "wrapped": true
+ },
+ "items": {
+ "$ref": "#/components/schemas/Tag"
+ }
+ },
+ "status": {
+ "type": "string",
+ "description": "pet status in the store",
+ "enum": [
+ "available",
+ "pending",
+ "sold"
+ ]
+ }
+ },
+ "xml": {
+ "name": "Pet"
+ }
+ },
+ "ApiResponse": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "securitySchemes": {
+ "petstore_auth": {
+ "type": "oauth2",
+ "flows": {
+ "implicit": {
+ "authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets"
+ }
+ }
+ }
+ },
+ "api_key": {
+ "type": "apiKey",
+ "name": "api_key",
+ "in": "header"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/schema_classes/__init__.py b/tests/schema_classes/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/schema_classes/test_schema.py b/tests/schema_classes/test_schema.py
new file mode 100644
index 0000000..ecbe96f
--- /dev/null
+++ b/tests/schema_classes/test_schema.py
@@ -0,0 +1,64 @@
+import logging
+
+from pydantic import BaseModel
+
+from openapi_pydantic import Reference, schema_validate
+from openapi_pydantic.compat import (
+ DEFS_KEY,
+ PYDANTIC_V2,
+ ConfigDict,
+ Extra,
+ models_json_schema,
+ v1_schema,
+)
+
+
+def test_schema() -> None:
+ schema = schema_validate(
+ {
+ "title": "reference list",
+ "description": "schema for list of reference type",
+ "allOf": [{"$ref": "#/definitions/TestType"}],
+ }
+ )
+ logging.debug(f"schema.allOf={schema.allOf}")
+ assert schema.allOf
+ assert isinstance(schema.allOf, list)
+ assert isinstance(schema.allOf[0], Reference)
+ assert schema.allOf[0].ref == "#/definitions/TestType"
+
+
+def test_additional_properties_is_bool() -> None:
+ class TestModel(BaseModel):
+ test_field: str
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+
+ else:
+
+ class Config:
+ extra = Extra.forbid
+
+ if PYDANTIC_V2:
+ _key_map, schema_definition = models_json_schema([(TestModel, "validation")])
+ else:
+ schema_definition = v1_schema([TestModel])
+
+ assert schema_definition == {
+ DEFS_KEY: {
+ "TestModel": {
+ "title": "TestModel",
+ "type": "object",
+ "properties": {"test_field": {"title": "Test Field", "type": "string"}},
+ "required": ["test_field"],
+ "additionalProperties": False,
+ }
+ }
+ }
+
+ # allow "additionalProperties" to have boolean value
+ result = schema_validate(schema_definition[DEFS_KEY]["TestModel"])
+ assert result.additionalProperties is False
diff --git a/tests/schema_classes/test_security_scheme.py b/tests/schema_classes/test_security_scheme.py
new file mode 100644
index 0000000..b395af2
--- /dev/null
+++ b/tests/schema_classes/test_security_scheme.py
@@ -0,0 +1,46 @@
+from openapi_pydantic import SecurityScheme
+from openapi_pydantic.compat import PYDANTIC_V2
+
+
+def test_oidc_parsing() -> None:
+ security_scheme_1 = SecurityScheme(
+ type="openIdConnect", openIdConnectUrl="https://example.com/openIdConnect"
+ )
+ assert isinstance(security_scheme_1.openIdConnectUrl, str)
+ dump1 = getattr(security_scheme_1, "model_dump_json" if PYDANTIC_V2 else "json")
+ if PYDANTIC_V2:
+ assert dump1(by_alias=True, exclude_none=True) == (
+ '{"type":"openIdConnect","openIdConnectUrl":"https://example.com/openIdConnect"}'
+ )
+ else:
+ assert dump1(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)
+ dump2 = getattr(security_scheme_2, "model_dump_json" if PYDANTIC_V2 else "json")
+ if PYDANTIC_V2:
+ assert dump2(by_alias=True, exclude_none=True) == (
+ '{"type":"openIdConnect","openIdConnectUrl":"/openIdConnect"}'
+ )
+ else:
+ assert dump2(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)
+ dump3 = getattr(security_scheme_3, "model_dump_json" if PYDANTIC_V2 else "json")
+ if PYDANTIC_V2:
+ assert dump3(by_alias=True, exclude_none=True) == (
+ '{"type":"openIdConnect","openIdConnectUrl":"openIdConnect"}'
+ )
+ else:
+ assert dump3(by_alias=True, exclude_none=True) == (
+ '{"type": "openIdConnect", "openIdConnectUrl": "openIdConnect"}'
+ )
diff --git a/tests/test_alias.py b/tests/test_alias.py
new file mode 100644
index 0000000..04238d6
--- /dev/null
+++ b/tests/test_alias.py
@@ -0,0 +1,78 @@
+from typing import Callable
+
+from openapi_pydantic import (
+ MediaType,
+ Parameter,
+ PathItem,
+ Reference,
+ Schema,
+ SecurityScheme,
+)
+from openapi_pydantic.compat import PYDANTIC_V2
+
+validate_func_name = "model_validate" if PYDANTIC_V2 else "parse_obj"
+
+
+def test_media_type_alias() -> None:
+ media_type_1 = MediaType(media_type_schema=Schema())
+ media_type_2 = MediaType(schema=Schema())
+ model_validate: Callable[[dict], MediaType] = getattr(MediaType, validate_func_name)
+ media_type_3 = model_validate({"media_type_schema": Schema()})
+ media_type_4 = model_validate({"schema": Schema()})
+ assert media_type_1 == media_type_2 == media_type_3 == media_type_4
+
+
+def test_parameter_alias() -> None:
+ parameter_1 = Parameter( # type: ignore
+ name="test",
+ param_in="path",
+ param_schema=Schema(),
+ )
+ parameter_2 = Parameter( # type: ignore
+ name="test",
+ param_in="path",
+ schema=Schema(),
+ )
+ model_validate: Callable[[dict], Parameter] = getattr(Parameter, validate_func_name)
+ parameter_3 = model_validate(
+ {"name": "test", "param_in": "path", "param_schema": Schema()}
+ )
+ parameter_4 = model_validate({"name": "test", "in": "path", "schema": Schema()})
+ assert parameter_1 == parameter_2 == parameter_3 == parameter_4
+
+
+def test_path_item_alias() -> None:
+ path_item_1 = PathItem(ref="#/dummy")
+ model_validate: Callable[[dict], PathItem] = getattr(PathItem, validate_func_name)
+ path_item_2 = model_validate({"ref": "#/dummy"})
+ path_item_3 = model_validate({"$ref": "#/dummy"})
+ assert path_item_1 == path_item_2 == path_item_3
+
+
+def test_reference_alias() -> None:
+ reference_1 = Reference(ref="#/dummy") # type: ignore
+ reference_2 = Reference(**{"$ref": "#/dummy"})
+ model_validate: Callable[[dict], Reference] = getattr(Reference, validate_func_name)
+ reference_3 = model_validate({"ref": "#/dummy"})
+ reference_4 = model_validate({"$ref": "#/dummy"})
+ assert reference_1 == reference_2 == reference_3 == reference_4
+
+
+def test_security_scheme() -> None:
+ security_scheme_1 = SecurityScheme(type="apiKey", security_scheme_in="header")
+ model_validate: Callable[[dict], SecurityScheme] = getattr(
+ SecurityScheme, validate_func_name
+ )
+ security_scheme_2 = model_validate(
+ {"type": "apiKey", "security_scheme_in": "header"}
+ )
+ security_scheme_3 = 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")
+ model_validate: Callable[[dict], Schema] = getattr(Schema, validate_func_name)
+ schema_2 = model_validate({"schema_not": Schema(), "schema_format": "email"})
+ schema_3 = model_validate({"not": Schema(), "format": "email"})
+ assert schema_1 == schema_2 == schema_3
diff --git a/tests/test_config_example.py b/tests/test_config_example.py
new file mode 100644
index 0000000..5247f79
--- /dev/null
+++ b/tests/test_config_example.py
@@ -0,0 +1,95 @@
+from typing import Any
+
+from openapi_pydantic import (
+ XML,
+ Callback,
+ Components,
+ Contact,
+ Discriminator,
+ Encoding,
+ Example,
+ ExternalDocumentation,
+ Header,
+ Info,
+ License,
+ Link,
+ MediaType,
+ OAuthFlow,
+ OAuthFlows,
+ OpenAPI,
+ Operation,
+ Parameter,
+ PathItem,
+ Paths,
+ Reference,
+ RequestBody,
+ Response,
+ Responses,
+ Schema,
+ SecurityRequirement,
+ SecurityScheme,
+ Server,
+ ServerVariable,
+ Tag,
+)
+from openapi_pydantic.compat import PYDANTIC_V2
+
+
+def test_config_example() -> None:
+ all_types = [
+ OpenAPI,
+ Info,
+ Contact,
+ License,
+ Server,
+ ServerVariable,
+ Components,
+ Paths,
+ PathItem,
+ Operation,
+ ExternalDocumentation,
+ Parameter,
+ RequestBody,
+ MediaType,
+ Encoding,
+ Responses,
+ Response,
+ Callback,
+ Example,
+ Link,
+ Header,
+ Tag,
+ Reference,
+ Schema,
+ Discriminator,
+ XML,
+ SecurityScheme,
+ OAuthFlows,
+ OAuthFlow,
+ SecurityRequirement,
+ ]
+ for schema_type in all_types:
+ _assert_config_examples(schema_type)
+
+
+def _assert_config_examples(schema_type: Any) -> None:
+ if PYDANTIC_V2:
+ if not hasattr(schema_type, "model_config"):
+ return
+ extra = schema_type.model_config.get("json_schema_extra")
+ if extra is not None:
+ examples = extra["examples"]
+ if examples is None:
+ breakpoint()
+ for example_dict in examples:
+ obj = schema_type.model_validate(example_dict)
+ assert obj.model_fields_set
+
+ else:
+ Config = getattr(schema_type, "Config", None)
+ schema_extra = getattr(Config, "schema_extra", None)
+ if schema_extra is not None:
+ examples = schema_extra["examples"]
+ for example_dict in examples:
+ obj = schema_type(**example_dict)
+ assert obj.__fields_set__
diff --git a/tests/test_example.py b/tests/test_example.py
new file mode 100644
index 0000000..1f3c8b7
--- /dev/null
+++ b/tests/test_example.py
@@ -0,0 +1,67 @@
+import logging
+from typing import Callable
+
+from openapi_pydantic import Info, OpenAPI, Operation, PathItem, Response
+from openapi_pydantic.compat import PYDANTIC_V2
+
+
+def test_readme_example() -> None:
+ open_api_1 = readme_example_1()
+ assert open_api_1
+ dump_json = getattr(open_api_1, "model_dump_json" if PYDANTIC_V2 else "json")
+ open_api_json_1 = dump_json(by_alias=True, exclude_none=True, indent=2)
+ logging.debug(open_api_json_1)
+ assert open_api_json_1
+
+ open_api_2 = readme_example_2()
+ assert open_api_1 == open_api_2
+
+ open_api_3 = readme_example_3()
+ assert open_api_1 == open_api_3
+
+
+def readme_example_1() -> OpenAPI:
+ """Construct OpenAPI using data class"""
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ get=Operation(responses={"200": Response(description="pong")})
+ )
+ },
+ )
+
+
+def readme_example_2() -> OpenAPI:
+ """Construct OpenAPI from raw data object"""
+ openapi_validate: Callable[[dict], OpenAPI] = getattr(
+ OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ return openapi_validate(
+ {
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {"get": {"responses": {"200": {"description": "pong"}}}}
+ },
+ }
+ )
+
+
+def readme_example_3() -> OpenAPI:
+ """Construct OpenAPI from mixed object"""
+ openapi_validate: Callable[[dict], OpenAPI] = getattr(
+ OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ return openapi_validate(
+ {
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": PathItem(
+ get={"responses": {"200": Response(description="pong")}}
+ )
+ },
+ }
+ )
diff --git a/tests/test_openapi.py b/tests/test_openapi.py
new file mode 100644
index 0000000..536e34a
--- /dev/null
+++ b/tests/test_openapi.py
@@ -0,0 +1,81 @@
+from typing import Callable
+
+import pytest
+
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.v3 import v3_0, v3_1
+
+
+@pytest.mark.parametrize("version", ["3.0.4", "3.1.1"])
+def test_parse_with_callback(version: str) -> None:
+ data = {
+ "openapi": version,
+ "info": {"title": "API with Callback", "version": ""},
+ "paths": {
+ "/create": {
+ "post": {
+ "responses": {"200": {"description": "Success"}},
+ "callbacks": {
+ "event": {
+ "callback": {
+ "post": {
+ "responses": {"200": {"description": "Success"}}
+ }
+ }
+ }
+ },
+ }
+ }
+ },
+ }
+
+ if version == "3.0.4":
+ model_validate_3_0: Callable[[dict], v3_0.OpenAPI] = getattr(
+ v3_0.OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ assert model_validate_3_0(data) == v3_0.OpenAPI(
+ info=v3_0.Info(title="API with Callback", version=""),
+ paths={
+ "/create": v3_0.PathItem(
+ post=v3_0.Operation(
+ responses={"200": v3_0.Response(description="Success")},
+ callbacks={
+ "event": {
+ "callback": v3_0.PathItem(
+ post=v3_0.Operation(
+ responses={
+ "200": v3_0.Response(description="Success")
+ }
+ )
+ )
+ }
+ },
+ )
+ )
+ },
+ )
+ else:
+ model_validate_3_1: Callable[[dict], v3_1.OpenAPI] = getattr(
+ v3_1.OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ assert model_validate_3_1(data) == v3_1.OpenAPI(
+ info=v3_1.Info(title="API with Callback", version=""),
+ paths={
+ "/create": v3_1.PathItem(
+ post=v3_1.Operation(
+ responses={"200": v3_1.Response(description="Success")},
+ callbacks={
+ "event": {
+ "callback": v3_1.PathItem(
+ post=v3_1.Operation(
+ responses={
+ "200": v3_1.Response(description="Success")
+ }
+ )
+ )
+ }
+ },
+ )
+ )
+ },
+ )
diff --git a/tests/test_parse.py b/tests/test_parse.py
new file mode 100644
index 0000000..0a48140
--- /dev/null
+++ b/tests/test_parse.py
@@ -0,0 +1,42 @@
+from typing import Literal
+
+import pytest
+
+from openapi_pydantic import parse_obj
+from openapi_pydantic.v3 import v3_0, v3_1
+
+
+@pytest.mark.parametrize("version", ["3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0"])
+def test_parse_obj_3_0(
+ version: Literal["3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0"]
+) -> None:
+ result = parse_obj(
+ {
+ "openapi": version,
+ "info": {"title": "foo", "version": "0.1.0"},
+ "paths": {"/": {}},
+ }
+ )
+
+ assert result == v3_0.OpenAPI(
+ openapi=version,
+ info=v3_0.Info(title="foo", version="0.1.0"),
+ paths={"/": v3_0.PathItem()},
+ )
+
+
+@pytest.mark.parametrize("version", ["3.1.1", "3.1.0"])
+def test_parse_obj_3_1(version: Literal["3.1.1", "3.1.0"]) -> None:
+ result = parse_obj(
+ {
+ "openapi": version,
+ "info": {"title": "foo", "version": "0.1.0"},
+ "paths": {"/": {}},
+ }
+ )
+
+ assert result == v3_1.OpenAPI(
+ openapi=version,
+ info=v3_1.Info(title="foo", version="0.1.0"),
+ paths={"/": v3_1.PathItem()},
+ )
diff --git a/tests/test_swagger_openapi_v3.py b/tests/test_swagger_openapi_v3.py
new file mode 100644
index 0000000..a6214e3
--- /dev/null
+++ b/tests/test_swagger_openapi_v3.py
@@ -0,0 +1,47 @@
+from typing import Dict, Optional
+
+from pydantic import Field
+
+from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict
+from openapi_pydantic.v3.v3_0 import OpenAPI, Operation, PathItem
+
+
+def test_swagger_openapi_v3() -> None:
+ with open("tests/data/swagger_openapi_v3.0.1.json") as f:
+ if PYDANTIC_V2:
+ validate = getattr(ExtendedOpenAPI, "model_validate_json")
+ else:
+ validate = getattr(ExtendedOpenAPI, "parse_raw")
+ open_api = validate(f.read())
+ assert open_api
+
+
+class ExtendedOperation(Operation):
+ """Override classes to use "x-codegen-request-body-name" in Operation"""
+
+ xCodegenRequestBodyName: Optional[str] = Field(
+ default=None, alias="x-codegen-request-body-name"
+ )
+
+ if PYDANTIC_V2:
+ model_config = ConfigDict(populate_by_name=True)
+
+ else:
+
+ class Config:
+ allow_population_by_field_name = True
+
+
+class ExtendedPathItem(PathItem):
+ get: Optional[ExtendedOperation] = None
+ put: Optional[ExtendedOperation] = None
+ post: Optional[ExtendedOperation] = None
+ delete: Optional[ExtendedOperation] = None
+ options: Optional[ExtendedOperation] = None
+ head: Optional[ExtendedOperation] = None
+ patch: Optional[ExtendedOperation] = None
+ trace: Optional[ExtendedOperation] = None
+
+
+class ExtendedOpenAPI(OpenAPI):
+ paths: Dict[str, ExtendedPathItem] # type: ignore[assignment]
diff --git a/tests/util/__init__.py b/tests/util/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/util/test_optional_and_computed.py b/tests/util/test_optional_and_computed.py
new file mode 100644
index 0000000..d8b95f8
--- /dev/null
+++ b/tests/util/test_optional_and_computed.py
@@ -0,0 +1,114 @@
+# mypy: ignore-errors
+
+from typing import Optional
+
+import pytest
+
+from openapi_pydantic import (
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="computed fields require Pydantic V2")
+def test_optional_and_computed_fields() -> None:
+ api = construct_sample_api()
+
+ result = construct_open_api_with_schema_class(api)
+ assert result.components is not None
+ assert result.components.schemas is not None
+
+ req_schema = result.components.schemas["SampleRequest"]
+ assert isinstance(req_schema, Schema)
+ assert req_schema.properties is not None
+ assert req_schema.required is not None
+
+ resp_schema = result.components.schemas["SampleResponse"]
+ assert isinstance(resp_schema, Schema)
+ assert resp_schema.properties is not None
+ assert resp_schema.required is not None
+
+ # When validating:
+ # - required fields are still required
+ # - optional fields are still optional
+ # - computed fields don't exist
+ assert "req" in req_schema.properties
+ assert "opt" in req_schema.properties
+ assert "comp" not in req_schema.properties
+ assert set(req_schema.required) == {"req"}
+
+ # When serializing:
+ # - required fields are still required
+ # - optional fields are still optional
+ # (except when json_schema_serialization_defaults_required is enabled)
+ # - computed fields are required
+ assert "req" in resp_schema.properties
+ assert "opt" in resp_schema.properties
+ assert "comp" in resp_schema.properties
+ assert set(resp_schema.required) == {"req", "comp"}
+
+
+def construct_sample_api() -> OpenAPI:
+ from typing import TYPE_CHECKING, Callable
+
+ from pydantic import BaseModel
+
+ if TYPE_CHECKING:
+
+ def computed_field(x: Callable) -> Callable: ...
+
+ else:
+ from pydantic import computed_field
+
+ class SampleModel(BaseModel):
+ req: bool
+ opt: Optional[bool] = None
+
+ @computed_field
+ @property
+ def comp(self) -> bool:
+ return True
+
+ class SampleRequest(SampleModel):
+ model_config = {"json_schema_mode": "validation"}
+
+ class SampleResponse(SampleModel):
+ model_config = {"json_schema_mode": "serialization"}
+
+ return OpenAPI(
+ info=Info(
+ title="Sample API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/callme": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleRequest)
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="resp",
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleResponse)
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
diff --git a/tests/util/test_pydantic_field.py b/tests/util/test_pydantic_field.py
new file mode 100644
index 0000000..2d9e583
--- /dev/null
+++ b/tests/util/test_pydantic_field.py
@@ -0,0 +1,163 @@
+from typing import Dict, List, Union
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+from openapi_pydantic import (
+ Discriminator,
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ Reference,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.compat import (
+ DEFS_KEY,
+ PYDANTIC_MINOR_VERSION,
+ PYDANTIC_V2,
+ models_json_schema,
+ v1_schema,
+)
+from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
+
+
+class DataAModel(BaseModel):
+ kind: Literal["a"]
+
+
+class DataBModel(BaseModel):
+ kind: Literal["b"]
+
+
+class RequestModel(BaseModel):
+ data: Union[DataAModel, DataBModel] = Field(discriminator="kind")
+
+
+def construct_base_open_api() -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=RequestModel
+ )
+ )
+ }
+ ),
+ responses={"200": Response(description="pong")},
+ )
+ )
+ },
+ )
+
+
+def test_pydantic_discriminator_schema_generation() -> None:
+ """https://github.com/kuimono/openapi-schema-pydantic/issues/8"""
+
+ a_kind: Dict[str, Union[str, List[str]]] = {"title": "Kind", "type": "string"}
+ b_kind: Dict[str, Union[str, List[str]]] = {"title": "Kind", "type": "string"}
+
+ if PYDANTIC_V2:
+ _key_map, json_schema = models_json_schema([(RequestModel, "validation")])
+ # In Pydantic v2, string literal types are mapped to consts.
+ a_kind["const"] = "a"
+ b_kind["const"] = "b"
+ if PYDANTIC_MINOR_VERSION < 10:
+ # Prior to 2.10, string literal types are also mapped to enums with a single entry.
+ a_kind["enum"] = ["a"]
+ b_kind["enum"] = ["b"]
+ else:
+ json_schema = v1_schema([RequestModel])
+ # In Pydantic v1, string literal types are mapped to enums with a single entry.
+ a_kind["enum"] = ["a"]
+ b_kind["enum"] = ["b"]
+
+ assert json_schema == {
+ DEFS_KEY: {
+ "DataAModel": {
+ "properties": {
+ "kind": a_kind,
+ },
+ "required": ["kind"],
+ "title": "DataAModel",
+ "type": "object",
+ },
+ "DataBModel": {
+ "properties": {
+ "kind": b_kind,
+ },
+ "required": ["kind"],
+ "title": "DataBModel",
+ "type": "object",
+ },
+ "RequestModel": {
+ "properties": {
+ "data": {
+ "oneOf": [
+ {"$ref": f"#/{DEFS_KEY}/DataAModel"},
+ {"$ref": f"#/{DEFS_KEY}/DataBModel"},
+ ],
+ "discriminator": {
+ "mapping": {
+ "a": f"#/{DEFS_KEY}/DataAModel",
+ "b": f"#/{DEFS_KEY}/DataBModel",
+ },
+ "propertyName": "kind",
+ },
+ "title": "Data",
+ }
+ },
+ "required": ["data"],
+ "title": "RequestModel",
+ "type": "object",
+ },
+ }
+ }
+
+
+def test_pydantic_discriminator_openapi_generation() -> None:
+ """https://github.com/kuimono/openapi-schema-pydantic/issues/8"""
+
+ open_api = construct_open_api_with_schema_class(construct_base_open_api())
+ assert open_api.components is not None
+ assert open_api.components.schemas is not None
+ json_schema = open_api.components.schemas["RequestModel"]
+ assert json_schema.properties == {
+ "data": Schema(
+ oneOf=[
+ Reference(
+ **{
+ "$ref": "#/components/schemas/DataAModel",
+ "summary": None,
+ "description": None,
+ }
+ ),
+ Reference(
+ **{
+ "$ref": "#/components/schemas/DataBModel",
+ "summary": None,
+ "description": None,
+ }
+ ),
+ ],
+ title="Data",
+ discriminator=Discriminator(
+ propertyName="kind",
+ mapping={
+ "a": "#/components/schemas/DataAModel",
+ "b": "#/components/schemas/DataBModel",
+ },
+ ),
+ )
+ }
diff --git a/tests/util/test_util.py b/tests/util/test_util.py
new file mode 100644
index 0000000..bed4270
--- /dev/null
+++ b/tests/util/test_util.py
@@ -0,0 +1,271 @@
+import logging
+from typing import Callable, Generic, TypeVar
+
+import pytest
+from pydantic import BaseModel, Field
+
+from openapi_pydantic import (
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ Reference,
+ RequestBody,
+ Response,
+)
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
+
+
+def test_construct_open_api_with_schema_class_1() -> None:
+ open_api = construct_base_open_api_1()
+ result_open_api_1 = construct_open_api_with_schema_class(open_api)
+ result_open_api_2 = construct_open_api_with_schema_class(
+ open_api, [PingRequest, PingResponse]
+ )
+ assert result_open_api_1.components == result_open_api_2.components
+ assert result_open_api_1 == result_open_api_2
+
+ dump_json = getattr(result_open_api_1, "model_dump_json" if PYDANTIC_V2 else "json")
+ open_api_json = dump_json(by_alias=True, exclude_none=True, indent=2)
+ logging.debug(open_api_json)
+
+
+def test_construct_open_api_with_schema_class_2() -> None:
+ open_api_1 = construct_base_open_api_1()
+ open_api_2 = construct_base_open_api_2()
+ result_open_api_1 = construct_open_api_with_schema_class(open_api_1)
+ result_open_api_2 = construct_open_api_with_schema_class(
+ open_api_2, [PingRequest, PingResponse]
+ )
+ assert result_open_api_1 == result_open_api_2
+
+
+def test_construct_open_api_with_schema_class_3() -> None:
+ open_api_3 = construct_base_open_api_3()
+
+ result_with_alias_1 = construct_open_api_with_schema_class(open_api_3)
+ assert result_with_alias_1.components is not None
+ assert result_with_alias_1.components.schemas is not None
+ schema_with_alias = result_with_alias_1.components.schemas["PongResponse"]
+ assert schema_with_alias.properties is not None
+ assert "pong_foo" in schema_with_alias.properties
+ assert "pong_bar" in schema_with_alias.properties
+
+ result_with_alias_2 = construct_open_api_with_schema_class(
+ open_api_3, by_alias=True
+ )
+ assert result_with_alias_1 == result_with_alias_2
+
+ result_without_alias = construct_open_api_with_schema_class(
+ open_api_3, by_alias=False
+ )
+ assert result_without_alias.components is not None
+ assert result_without_alias.components.schemas is not None
+ schema_without_alias = result_without_alias.components.schemas["PongResponse"]
+ assert schema_without_alias.properties is not None
+ assert "resp_foo" in schema_without_alias.properties
+ assert "resp_bar" in schema_without_alias.properties
+
+
+@pytest.mark.skipif(PYDANTIC_V2, reason="generic type for Pydantic V1")
+def test_construct_open_api_with_schema_class_4_generic_response_v1() -> None:
+ DataT = TypeVar("DataT")
+ from pydantic.v1.generics import GenericModel
+
+ class GenericResponse(GenericModel, Generic[DataT]):
+ msg: str = Field(description="message of the generic response")
+ data: DataT = Field(description="data value of the generic response")
+
+ open_api_4 = construct_base_open_api_4_generic_response(
+ GenericResponse[PongResponse]
+ )
+
+ result = construct_open_api_with_schema_class(open_api_4)
+ assert result.components is not None
+ assert result.components.schemas is not None
+ assert "GenericResponse_PongResponse_" in result.components.schemas
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="generic type for Pydantic V2")
+def test_construct_open_api_with_schema_class_4_generic_response_v2() -> None:
+ DataT = TypeVar("DataT")
+
+ class GenericResponse(BaseModel, Generic[DataT]):
+ msg: str = Field(description="message of the generic response")
+ data: DataT = Field(description="data value of the generic response")
+
+ open_api_4 = construct_base_open_api_4_generic_response(
+ GenericResponse[PongResponse]
+ )
+
+ result = construct_open_api_with_schema_class(open_api_4)
+ assert result.components is not None
+ assert result.components.schemas is not None
+ assert "GenericResponse_PongResponse_" in result.components.schemas
+
+
+def construct_base_open_api_1() -> OpenAPI:
+ model_validate: Callable[[dict], OpenAPI] = getattr(
+ OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ return model_validate(
+ {
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {
+ "post": {
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": PydanticSchema(schema_class=PingRequest)
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "pong",
+ "content": {
+ "application/json": {
+ "schema": PydanticSchema(
+ schema_class=PingResponse
+ )
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ }
+ )
+
+
+def construct_base_open_api_2() -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=Reference(
+ **{"$ref": "#/components/schemas/PingRequest"}
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=Reference(
+ **{"$ref": "#/components/schemas/PingResponse"}
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+def construct_base_open_api_3() -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PingRequest
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PongResponse
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+def construct_base_open_api_4_generic_response(response_schema: type) -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PingRequest
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=response_schema
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+class PingRequest(BaseModel):
+ """Ping Request"""
+
+ req_foo: str = Field(description="foo value of the request")
+ req_bar: str = Field(description="bar value of the request")
+
+
+class PingResponse(BaseModel):
+ """Ping response"""
+
+ resp_foo: str = Field(description="foo value of the response")
+ resp_bar: str = Field(description="bar value of the response")
+
+
+class PongResponse(BaseModel):
+ """Pong response"""
+
+ resp_foo: str = Field(alias="pong_foo", description="foo value of the response")
+ resp_bar: str = Field(alias="pong_bar", description="bar value of the response")
diff --git a/tests/util/test_validated_schema.py b/tests/util/test_validated_schema.py
new file mode 100644
index 0000000..b4ab0ed
--- /dev/null
+++ b/tests/util/test_validated_schema.py
@@ -0,0 +1,117 @@
+# mypy: ignore-errors
+
+import sys
+from typing import Any, Optional
+
+import pytest
+from openapi_spec_validator import validate
+from pydantic import BaseModel
+
+from openapi_pydantic import (
+ DataType,
+ Header,
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.util import PydanticSchema, construct_open_api_with_schema_class
+
+if sys.version_info < (3, 9):
+ from typing_extensions import Annotated, Literal
+else:
+ from typing import Annotated, Literal
+
+
+def test_basic_schema() -> None:
+ class SampleModel(BaseModel):
+ required: bool
+ optional: Optional[bool] = None
+ one_literal_choice: Literal["only_choice"]
+ multiple_literal_choices: Literal["choice1", "choice2"]
+
+ part_api = construct_sample_api(SampleModel)
+
+ api = construct_open_api_with_schema_class(part_api)
+ assert api.components is not None
+ assert api.components.schemas is not None
+
+ if PYDANTIC_V2:
+ json_api: Any = api.model_dump(mode="json", by_alias=True, exclude_none=True)
+ else:
+ json_api: Any = api.dict(by_alias=True, exclude_none=True)
+ validate(json_api)
+
+
+@pytest.mark.skipif(
+ not PYDANTIC_V2,
+ reason="Field-level JSON examples not supported in Pydantic V1",
+)
+def test_field_json_schema_example() -> None:
+ from pydantic import WithJsonSchema
+
+ Thing = Annotated[str, WithJsonSchema({"examples": ["thing1"]})]
+
+ class SampleModel(BaseModel):
+ a: Thing
+
+ part_api = construct_sample_api(SampleModel)
+
+ api = construct_open_api_with_schema_class(part_api)
+ assert api.components is not None
+ assert api.components.schemas is not None
+
+ json_api: Any = api.model_dump(mode="json", by_alias=True, exclude_none=True)
+ props = json_api["components"]["schemas"]["SampleRequest"]["properties"]
+ assert props["a"]["examples"] == ["thing1"]
+
+ validate(json_api)
+
+
+def construct_sample_api(SampleModel) -> OpenAPI:
+ class SampleRequest(SampleModel):
+ model_config = {"json_schema_mode": "validation"}
+
+ class SampleResponse(SampleModel):
+ model_config = {"json_schema_mode": "serialization"}
+
+ return OpenAPI(
+ info=Info(
+ title="Sample API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/callme": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleRequest)
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="resp",
+ headers={
+ "WWW-Authenticate": Header(
+ description="Indicate how to authenticate",
+ schema=Schema(type=DataType.STRING),
+ )
+ },
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleResponse)
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
diff --git a/tests/v3_0/__init__.py b/tests/v3_0/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/v3_0/test_config_example.py b/tests/v3_0/test_config_example.py
new file mode 100644
index 0000000..7f37594
--- /dev/null
+++ b/tests/v3_0/test_config_example.py
@@ -0,0 +1,93 @@
+from typing import Any
+
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.v3.v3_0 import (
+ XML,
+ Callback,
+ Components,
+ Contact,
+ Discriminator,
+ Encoding,
+ Example,
+ ExternalDocumentation,
+ Header,
+ Info,
+ License,
+ Link,
+ MediaType,
+ OAuthFlow,
+ OAuthFlows,
+ OpenAPI,
+ Operation,
+ Parameter,
+ PathItem,
+ Paths,
+ Reference,
+ RequestBody,
+ Response,
+ Responses,
+ Schema,
+ SecurityRequirement,
+ SecurityScheme,
+ Server,
+ ServerVariable,
+ Tag,
+)
+
+
+def test_config_example() -> None:
+ all_types = [
+ OpenAPI,
+ Info,
+ Contact,
+ License,
+ Server,
+ ServerVariable,
+ Components,
+ Paths,
+ PathItem,
+ Operation,
+ ExternalDocumentation,
+ Parameter,
+ RequestBody,
+ MediaType,
+ Encoding,
+ Responses,
+ Response,
+ Callback,
+ Example,
+ Link,
+ Header,
+ Tag,
+ Reference,
+ Schema,
+ Discriminator,
+ XML,
+ SecurityScheme,
+ OAuthFlows,
+ OAuthFlow,
+ SecurityRequirement,
+ ]
+ for schema_type in all_types:
+ _assert_config_examples(schema_type)
+
+
+def _assert_config_examples(schema_type: Any) -> None:
+ if PYDANTIC_V2:
+ if not hasattr(schema_type, "model_config"):
+ return
+ extra = schema_type.model_config.get("json_schema_extra")
+ if extra is not None:
+ examples = extra["examples"]
+ for example_dict in examples:
+ obj = schema_type.model_validate(example_dict)
+ assert obj.model_fields_set
+
+ else:
+ Config = getattr(schema_type, "Config", None)
+ schema_extra = getattr(Config, "schema_extra", None)
+ if schema_extra is not None:
+ examples = schema_extra["examples"]
+ for example_dict in examples:
+ obj = schema_type(**example_dict)
+ assert obj.__fields_set__
diff --git a/tests/v3_0/test_datatype.py b/tests/v3_0/test_datatype.py
new file mode 100644
index 0000000..c5afe97
--- /dev/null
+++ b/tests/v3_0/test_datatype.py
@@ -0,0 +1,34 @@
+import pytest
+from pydantic import ValidationError
+
+from openapi_pydantic.v3.v3_0 import Schema
+
+
+@pytest.mark.parametrize(
+ "datatype",
+ (
+ "string",
+ "number",
+ "integer",
+ "boolean",
+ "array",
+ "object",
+ ),
+)
+def test_good_types_parse_and_equate(datatype: str) -> None:
+ assert Schema(type=datatype).type == datatype
+
+
+def test_bad_types_raise_validation_errors() -> None:
+ with pytest.raises(ValidationError):
+ Schema(type="invalid")
+
+ with pytest.raises(ValidationError):
+ Schema(anyOf=[{"type": "invalid"}])
+
+ with pytest.raises(ValidationError):
+ Schema(
+ properties={
+ "a": Schema(type="invalid"),
+ },
+ )
diff --git a/tests/v3_0/test_optional_and_computed.py b/tests/v3_0/test_optional_and_computed.py
new file mode 100644
index 0000000..0518929
--- /dev/null
+++ b/tests/v3_0/test_optional_and_computed.py
@@ -0,0 +1,117 @@
+# mypy: ignore-errors
+
+from typing import Optional
+
+import pytest
+
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.v3.v3_0 import (
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.v3.v3_0.util import (
+ PydanticSchema,
+ construct_open_api_with_schema_class,
+)
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="computed fields require Pydantic V2")
+def test_optional_and_computed_fields() -> None:
+ api = construct_sample_api()
+
+ result = construct_open_api_with_schema_class(api)
+ assert result.components is not None
+ assert result.components.schemas is not None
+
+ req_schema = result.components.schemas["SampleRequest"]
+ assert isinstance(req_schema, Schema)
+ assert req_schema.properties is not None
+ assert req_schema.required is not None
+
+ resp_schema = result.components.schemas["SampleResponse"]
+ assert isinstance(resp_schema, Schema)
+ assert resp_schema.properties is not None
+ assert resp_schema.required is not None
+
+ # When validating:
+ # - required fields are still required
+ # - optional fields are still optional
+ # - computed fields don't exist
+ assert "req" in req_schema.properties
+ assert "opt" in req_schema.properties
+ assert "comp" not in req_schema.properties
+ assert set(req_schema.required) == {"req"}
+
+ # When serializing:
+ # - required fields are still required
+ # - optional fields are still optional
+ # (except when json_schema_serialization_defaults_required is enabled)
+ # - computed fields are required
+ assert "req" in resp_schema.properties
+ assert "opt" in resp_schema.properties
+ assert "comp" in resp_schema.properties
+ assert set(resp_schema.required) == {"req", "comp"}
+
+
+def construct_sample_api() -> OpenAPI:
+ from typing import TYPE_CHECKING, Callable
+
+ from pydantic import BaseModel
+
+ if TYPE_CHECKING:
+
+ def computed_field(x: Callable) -> Callable: ...
+
+ else:
+ from pydantic import computed_field
+
+ class SampleModel(BaseModel):
+ req: bool
+ opt: Optional[bool] = None
+
+ @computed_field # type: ignore
+ @property
+ def comp(self) -> bool:
+ return True
+
+ class SampleRequest(SampleModel):
+ model_config = {"json_schema_mode": "validation"}
+
+ class SampleResponse(SampleModel):
+ model_config = {"json_schema_mode": "serialization"}
+
+ return OpenAPI(
+ info=Info(
+ title="Sample API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/callme": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleRequest)
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="resp",
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleResponse)
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
diff --git a/tests/v3_0/test_util.py b/tests/v3_0/test_util.py
new file mode 100644
index 0000000..e752dc2
--- /dev/null
+++ b/tests/v3_0/test_util.py
@@ -0,0 +1,330 @@
+import logging
+from typing import Callable, Generic, Literal, TypeVar
+
+import pytest
+from pydantic import BaseModel, Field
+
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.v3.v3_0 import (
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ Reference,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.v3.v3_0.util import (
+ PydanticSchema,
+ construct_open_api_with_schema_class,
+)
+
+
+def test_construct_open_api_with_schema_class_1() -> None:
+ open_api = construct_base_open_api_1()
+ result_open_api_1 = construct_open_api_with_schema_class(open_api)
+ result_open_api_2 = construct_open_api_with_schema_class(
+ open_api, [PingRequest, PingResponse]
+ )
+ assert result_open_api_1.components == result_open_api_2.components
+ assert result_open_api_1 == result_open_api_2
+
+ dump_json = getattr(result_open_api_1, "model_dump_json" if PYDANTIC_V2 else "json")
+ open_api_json = dump_json(by_alias=True, exclude_none=True, indent=2)
+ logging.debug(open_api_json)
+
+
+def test_construct_open_api_with_schema_class_2() -> None:
+ open_api_1 = construct_base_open_api_1()
+ open_api_2 = construct_base_open_api_2()
+ result_open_api_1 = construct_open_api_with_schema_class(open_api_1)
+ result_open_api_2 = construct_open_api_with_schema_class(
+ open_api_2, [PingRequest, PingResponse]
+ )
+ assert result_open_api_1 == result_open_api_2
+
+
+def test_construct_open_api_with_schema_class_3() -> None:
+ open_api_3 = construct_base_open_api_3()
+
+ result_with_alias_1 = construct_open_api_with_schema_class(open_api_3)
+ assert result_with_alias_1.components is not None
+ assert result_with_alias_1.components.schemas is not None
+ schema_with_alias = result_with_alias_1.components.schemas["PongResponse"]
+ assert isinstance(schema_with_alias, Schema)
+ assert schema_with_alias.properties is not None
+ assert "pong_foo" in schema_with_alias.properties
+ assert "pong_bar" in schema_with_alias.properties
+
+ result_with_alias_2 = construct_open_api_with_schema_class(
+ open_api_3, by_alias=True
+ )
+ assert result_with_alias_1 == result_with_alias_2
+
+ result_without_alias = construct_open_api_with_schema_class(
+ open_api_3, by_alias=False
+ )
+ assert result_without_alias.components is not None
+ assert result_without_alias.components.schemas is not None
+ schema_without_alias = result_without_alias.components.schemas["PongResponse"]
+ assert isinstance(schema_without_alias, Schema)
+ assert schema_without_alias.properties is not None
+ assert "resp_foo" in schema_without_alias.properties
+ assert "resp_bar" in schema_without_alias.properties
+
+
+@pytest.mark.skipif(PYDANTIC_V2, reason="generic type for Pydantic V1")
+def test_construct_open_api_with_schema_class_4_generic_response_v1() -> None:
+ DataT = TypeVar("DataT")
+ from pydantic.v1.generics import GenericModel
+
+ class GenericResponse(GenericModel, Generic[DataT]):
+ msg: str = Field(description="message of the generic response")
+ data: DataT = Field(description="data value of the generic response")
+
+ open_api_4 = construct_base_open_api_4_generic_response(
+ GenericResponse[PongResponse]
+ )
+
+ result = construct_open_api_with_schema_class(open_api_4)
+ assert result.components is not None
+ assert result.components.schemas is not None
+ assert "GenericResponse_PongResponse_" in result.components.schemas
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="generic type for Pydantic V2")
+def test_construct_open_api_with_schema_class_4_generic_response() -> None:
+ DataT = TypeVar("DataT")
+
+ class GenericResponse(BaseModel, Generic[DataT]):
+ msg: str = Field(description="message of the generic response")
+ data: DataT = Field(description="data value of the generic response")
+
+ open_api_4 = construct_base_open_api_4_generic_response(
+ GenericResponse[PongResponse]
+ )
+
+ result = construct_open_api_with_schema_class(open_api_4)
+ assert result.components is not None
+ assert result.components.schemas is not None
+ assert "GenericResponse_PongResponse_" in result.components.schemas
+
+
+def construct_base_open_api_1() -> OpenAPI:
+ model_validate: Callable[[dict], OpenAPI] = getattr(
+ OpenAPI, "model_validate" if PYDANTIC_V2 else "parse_obj"
+ )
+ return model_validate(
+ {
+ "info": {"title": "My own API", "version": "v0.0.1"},
+ "paths": {
+ "/ping": {
+ "post": {
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": PydanticSchema(schema_class=PingRequest)
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "pong",
+ "content": {
+ "application/json": {
+ "schema": PydanticSchema(
+ schema_class=PingResponse
+ )
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ }
+ )
+
+
+def construct_base_open_api_2() -> OpenAPI:
+ return OpenAPI(
+ info=Info(title="My own API", version="v0.0.1"),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=Reference(
+ **{"$ref": "#/components/schemas/PingRequest"}
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=Reference(
+ **{"$ref": "#/components/schemas/PingResponse"}
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+def construct_base_open_api_3() -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PingRequest
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PongResponse
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+def construct_base_open_api_3_plus() -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PingPlusRequest
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PongResponse
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+def construct_base_open_api_4_generic_response(response_schema: type) -> OpenAPI:
+ return OpenAPI(
+ info=Info(
+ title="My own API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/ping": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=PingRequest
+ )
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="pong",
+ content={
+ "application/json": MediaType(
+ media_type_schema=PydanticSchema(
+ schema_class=response_schema
+ )
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ )
+
+
+class PingRequest(BaseModel):
+ """Ping Request"""
+
+ req_foo: str = Field(description="foo value of the request")
+ req_bar: str = Field(description="bar value of the request")
+
+
+class PingResponse(BaseModel):
+ """Ping response"""
+
+ resp_foo: str = Field(description="foo value of the response")
+ resp_bar: str = Field(description="bar value of the response")
+
+
+class PongResponse(BaseModel):
+ """Pong response"""
+
+ resp_foo: str = Field(alias="pong_foo", description="foo value of the response")
+ resp_bar: str = Field(alias="pong_bar", description="bar value of the response")
+
+
+class PingPlusRequest(BaseModel):
+ """Ping Request with extra"""
+
+ req_foo: str
+ req_bar: str
+ req_single_choice: Literal["one"]
+
+
+def test_enum_with_single_choice() -> None:
+ api_obj = construct_open_api_with_schema_class(construct_base_open_api_3_plus())
+ model_dump = getattr(api_obj, "model_dump" if PYDANTIC_V2 else "dict")
+ api = model_dump(by_alias=True, exclude_none=True)
+ schema = api["components"]["schemas"]["PingPlusRequest"]
+ prop = schema["properties"]["req_single_choice"]
+ # OpenAPI 3.0 does not support "const", so make sure the enum is not
+ # rendered that way.
+ assert not prop.get("const")
+ assert prop["enum"] == ["one"]
diff --git a/tests/v3_0/test_validated_schema.py b/tests/v3_0/test_validated_schema.py
new file mode 100644
index 0000000..113248f
--- /dev/null
+++ b/tests/v3_0/test_validated_schema.py
@@ -0,0 +1,97 @@
+# mypy: ignore-errors
+
+import sys
+from typing import Any, Optional
+
+from openapi_spec_validator import validate
+from pydantic import BaseModel
+
+from openapi_pydantic.compat import PYDANTIC_V2
+from openapi_pydantic.v3.v3_0 import (
+ Components,
+ DataType,
+ Example,
+ Header,
+ Info,
+ MediaType,
+ OpenAPI,
+ Operation,
+ PathItem,
+ RequestBody,
+ Response,
+ Schema,
+)
+from openapi_pydantic.v3.v3_0.util import (
+ PydanticSchema,
+ construct_open_api_with_schema_class,
+)
+
+if sys.version_info < (3, 9):
+ from typing_extensions import Literal
+else:
+ from typing import Literal
+
+
+def test_basic_schema() -> None:
+ class SampleModel(BaseModel):
+ required: bool
+ optional: Optional[bool] = None
+ one_literal_choice: Literal["only_choice"]
+ multiple_literal_choices: Literal["choice1", "choice2"]
+
+ part_api = construct_sample_api(SampleModel)
+
+ api = construct_open_api_with_schema_class(part_api)
+ assert api.components is not None
+ assert api.components.schemas is not None
+
+ if PYDANTIC_V2:
+ json_api: Any = api.model_dump(mode="json", by_alias=True, exclude_none=True)
+ else:
+ json_api: Any = api.dict(by_alias=True, exclude_none=True)
+ validate(json_api)
+
+
+def construct_sample_api(SampleModel) -> OpenAPI:
+ class SampleRequest(SampleModel):
+ model_config = {"json_schema_mode": "validation"}
+
+ class SampleResponse(SampleModel):
+ model_config = {"json_schema_mode": "serialization"}
+
+ return OpenAPI(
+ info=Info(
+ title="Sample API",
+ version="v0.0.1",
+ ),
+ paths={
+ "/callme": PathItem(
+ post=Operation(
+ requestBody=RequestBody(
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleRequest)
+ )
+ }
+ ),
+ responses={
+ "200": Response(
+ description="resp",
+ headers={
+ "WWW-Authenticate": Header(
+ description="Indicate how to authenticate",
+ schema=Schema(type=DataType.STRING),
+ )
+ },
+ content={
+ "application/json": MediaType(
+ schema=PydanticSchema(schema_class=SampleResponse)
+ )
+ },
+ )
+ },
+ )
+ )
+ },
+ components=Components(examples={"thing-example": Example(value="thing1")}),
+ )
diff --git a/tests/v3_1/__init__.py b/tests/v3_1/__init__.py
new file mode 100644
index 0000000..0699444
--- /dev/null
+++ b/tests/v3_1/__init__.py
@@ -0,0 +1,6 @@
+from openapi_pydantic.v3.v3_1.schema import Schema, schema_validate
+
+
+def test_empty_schema() -> None:
+ schema = schema_validate({})
+ assert schema == Schema()
diff --git a/tests/v3_1/test_datatype.py b/tests/v3_1/test_datatype.py
new file mode 100644
index 0000000..242d05a
--- /dev/null
+++ b/tests/v3_1/test_datatype.py
@@ -0,0 +1,35 @@
+import pytest
+from pydantic import ValidationError
+
+from openapi_pydantic.v3.v3_1 import Schema
+
+
+@pytest.mark.parametrize(
+ "datatype",
+ (
+ "string",
+ "number",
+ "integer",
+ "boolean",
+ "array",
+ "object",
+ "null",
+ ),
+)
+def test_good_types_parse_and_equate(datatype: str) -> None:
+ assert Schema(type=datatype).type == datatype
+
+
+def test_bad_types_raise_validation_errors() -> None:
+ with pytest.raises(ValidationError):
+ Schema(type="invalid")
+
+ with pytest.raises(ValidationError):
+ Schema(anyOf=[{"type": "invalid"}])
+
+ with pytest.raises(ValidationError):
+ Schema(
+ properties={
+ "a": Schema(type="invalid"),
+ },
+ )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..14d85c1
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,38 @@
+[tox]
+min_version = 4.0
+env_list = format, lint, py{38,39,310,311,312}-pydantic{1,2}-{test,type}
+
+[gh-actions]
+python =
+ 3.8: py38
+ 3.9: py39
+ 3.10: py310
+ 3.11: py311
+ 3.12: format, lint, py312
+
+[testenv]
+labels = core
+allowlist_externals = poetry
+# The "pydanticX:", "test:", and "type:" prefixes are Tox factor-conditional settings.
+# https://tox.wiki/en/3.4.0/config.html?highlight=conditional#factors-and-factor-conditional-settings
+# Note that "poetry add" changes pyproject.toml, but at least we
+# change it back when the tests finish.
+commands_pre =
+ pydantic1: poetry add --lock pydantic<2
+ pydantic2: poetry add --lock pydantic>=1.8
+ poetry install --no-root --all-extras
+commands =
+ test: poetry run pytest -vv tests
+ type: poetry run mypy openapi_pydantic tests
+
+[testenv:format]
+allowlist_externals = poetry
+commands_pre = poetry install --only dev --no-root
+commands =
+ poetry run black --check openapi_pydantic tests
+
+[testenv:lint]
+allowlist_externals = poetry
+commands_pre = poetry install --only dev --no-root
+commands =
+ poetry run ruff check openapi_pydantic tests