diff --git a/openapi_to_fastapi/model_generator.py b/openapi_to_fastapi/model_generator.py index 74dbbc1..e822c26 100644 --- a/openapi_to_fastapi/model_generator.py +++ b/openapi_to_fastapi/model_generator.py @@ -4,22 +4,44 @@ from contextlib import contextmanager, suppress from pathlib import Path -from datamodel_code_generator import PythonVersion +from datamodel_code_generator import DatetimeClassType, PythonVersion from datamodel_code_generator.model import pydantic_v2 as pydantic_model from datamodel_code_generator.parser.openapi import OpenAPIParser +from datamodel_code_generator.types import StrictTypes from openapi_to_fastapi.logger import logger -def generate_model_from_schema(schema: str, format_code: bool = False) -> str: +def generate_model_from_schema( + schema: str, + format_code: bool = False, + strict_validation: bool = False, +) -> str: """ Given an OpenAPI schema, generate pydantic models from everything defined in the "components/schemas" section :param schema: Content of an OpenAPI spec, plain text :param format_code: Whether to format generated code + :param strict_validation: Whether to use strict validation :return: Importable python code with generated models """ + if strict_validation: + strict_types = ( + StrictTypes.str, + StrictTypes.bytes, + StrictTypes.int, + StrictTypes.float, + StrictTypes.bool, + ) + else: + strict_types = None + + if strict_validation: + target_datetime_class = DatetimeClassType.Awaredatetime + else: + target_datetime_class = DatetimeClassType.Datetime + parser = OpenAPIParser( source=schema, data_model_type=pydantic_model.BaseModel, @@ -31,14 +53,21 @@ def generate_model_from_schema(schema: str, format_code: bool = False) -> str: extra_template_data=None, target_python_version=PythonVersion.PY_39, dump_resolve_reference_action=None, + extra_fields="forbid" if strict_validation else None, + strict_types=strict_types, field_constraints=False, snake_case_field=False, strip_default_none=False, aliases=None, + target_datetime_class=target_datetime_class, ) - result = parser.parse(format_=format_code) - return str(result) + result = str(parser.parse(format_=format_code)) + + if strict_validation: + result = override_with_stricter_dates(result) + + return result @contextmanager @@ -53,17 +82,23 @@ def _clean_tempfile(tmp_file, delete=True): def load_models( - schema: str, name: str = "", cleanup: bool = True, format_code: bool = False + schema: str, + name: str = "", + cleanup: bool = True, + format_code: bool = False, + strict_validation: bool = False, ): """ Generate pydantic models from OpenAPI spec and return a python module, which contains all the models from the "components/schemas" section. This function will create a dedicated python file in OS's temporary dir - and imports it + and imports it. + :param schema: OpenAPI spec, plain text :param name: Prefix for a module name, optional :param cleanup: Whether to remove a file with models afterwards :param format_code: Whether to format generated code + :param strict_validation: Whether to use strict validation :return: Module with pydantic models """ prefix = name.replace("/", "").replace(" ", "").replace("\\", "") + "_" @@ -73,7 +108,7 @@ def load_models( ), delete=cleanup, ) as tmp_file: - model_py = generate_model_from_schema(schema, format_code) + model_py = generate_model_from_schema(schema, format_code, strict_validation) tmp_file.write(model_py) if not cleanup: logger.info("Generated module %s: %s", name, tmp_file.name) @@ -84,3 +119,67 @@ def load_models( return spec.loader.load_module(module_name) else: raise ValueError(f"Failed to load module {module_name}") + + +def override_with_stricter_dates(file_content: str) -> str: + """ + Overrides the AwareDatetime and date in the python file by identifying the first + class definition (after the imports at the top) and injecting a comment and then + importing the StrictAwareDatetime as AwareDatetime and StrictDate as date which will + thus override the earlier imports. + + Example of the file before applying changes: + > from __future__ import annotations + > from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, StrictInt, ... + > from typing import List, Optional, Union + > from datetime import date + > + > + > class BadGateway(BaseModel): + > pass + > ... + + Example of file after changes: + > from __future__ import annotations + > from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, StrictInt, ... + > from typing import List, Optional, Union + > from datetime import date + > + > # Overriding the AwareDatetime and date with ones that do stricter validation + > from openapi_to_fastapi.pydantic_validators import StrictAwareDatetime as Aware... + > from openapi_to_fastapi.pydantic_validators import StrictDate as date + > + > + > class BadGateway(BaseModel): + > pass + > ... + + :param file_content: The file content as a string. + :return: The modified file content as a string. + """ + comment = ( + "# Overriding the AwareDatetime and date with ones that do stricter validation" + ) + import_strict_date_time = ( + "from openapi_to_fastapi.pydantic_validators import " + "StrictAwareDatetime as AwareDatetime" + ) + import_strict_date = ( + "from openapi_to_fastapi.pydantic_validators import StrictDate as date" + ) + + if "AwareDatetime" in file_content or "date" in file_content: + nl = "\n" + if "\r\n" in file_content: + nl = "\r\n" + + parts = file_content.partition(f"{nl}{nl}class ") + file_content = ( + f"{parts[0]}{nl}" + f"{comment}{nl}" + f"{import_strict_date_time}{nl}" + f"{import_strict_date}{nl}" + f"{parts[1]}{parts[2]}" + ) + + return file_content diff --git a/openapi_to_fastapi/pydantic_validators.py b/openapi_to_fastapi/pydantic_validators.py new file mode 100644 index 0000000..6743d47 --- /dev/null +++ b/openapi_to_fastapi/pydantic_validators.py @@ -0,0 +1,88 @@ +import re +from datetime import date +from typing import Annotated, Any + +from pydantic import AwareDatetime, BeforeValidator +from pydantic_core import PydanticCustomError + +rfc_3339_pattern = re.compile( + r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(Z|[\+-]\d{2}:\d{2})$" +) + +year_month_day_pattern = re.compile(r"^\d{4}-\d{2}-\d{2}$") + + +def strict_datetime_validator(value: Any) -> str: + """ + A function to be used as an extra before validator for a stricter version of the + AwareDatetime provided by pydantic. + + It aims to (together with AwareDatetime) only allow valid RFC 3339 date times. + + :param value: The value provided for the field. + :return: The string unchanged after validation. + """ + # When the before validators run, pydantic has not made any validation of the field + # just yet. The content can really be of any kind. + if not isinstance(value, str): + # This will (also) catch integers that would else be parsed as unix timestamps. + + raise PydanticCustomError( + "datetime_type", + "Input should be a valid datetime in RFC 3339 format, input is not a " + "string", + {"error": "input not string"}, + ) + + if not re.match(rfc_3339_pattern, value): + # Validates the format of the string strictly, but leaves things like how many + # days there is in a month, or hours in a day, etc. to AwareDatetime to check. + raise PydanticCustomError( + "datetime_from_date_parsing", + "Input should be a valid datetime, in RFC 3339 format", + {"error": "input does not follow RFC 3339"}, + ) + + return value + + +def strict_date_validator(value: Any) -> str: + """ + A function to be used as an extra before validator for a stricter validation of + dates. + + It aims to only allow dates of the form YYYY-MM-DD. + + :param value: The value provided for the field. + :return: The string unchanged after validation. + """ + # When the before validators run, pydantic has not made any validation of the field + # just yet. The content can really be of any kind. + if not isinstance(value, str): + # This will (also) catch integers that would else be parsed as unix timestamps. + + raise PydanticCustomError( + "date_type", + "Input should be a valid date in RFC 3339 'full-date' format, input is not " + "a string", + {"error": "input not string"}, + ) + + if not re.match(year_month_day_pattern, value): + # Validates the format of the string strictly, but leaves things like how many + # days there is in a month to the normal date class. + raise PydanticCustomError( + "date_from_datetime_parsing", + "Input should be a valid date, in RFC 3339 'full-date' format", + {"error": "input is not of form YYYY-MM-DD"}, + ) + + return value + + +StrictAwareDatetime = Annotated[ + AwareDatetime, BeforeValidator(strict_datetime_validator) +] + + +StrictDate = Annotated[date, BeforeValidator(strict_date_validator)] diff --git a/openapi_to_fastapi/routes.py b/openapi_to_fastapi/routes.py index 66a0ffa..548e536 100644 --- a/openapi_to_fastapi/routes.py +++ b/openapi_to_fastapi/routes.py @@ -118,9 +118,14 @@ def _validate_and_parse_specs(self, cleanup=True): raw_spec = spec_path.read_text(encoding="utf8") json_spec = json.loads(raw_spec) + strict_validation = bool(json_spec.get("x-strict-validation")) for path, path_item in parse_openapi_spec(json_spec).items(): models = load_models( - raw_spec, path, cleanup=cleanup, format_code=self._format_code + raw_spec, + path, + cleanup=cleanup, + format_code=self._format_code, + strict_validation=strict_validation, ) post = path_item.post if post: diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-int].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-int].json new file mode 100644 index 0000000..d8a7369 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-int].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 1, + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean", + "type": "bool_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].1.json new file mode 100644 index 0000000..2ecda7a --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].1.json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": null, + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean", + "type": "bool_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].json new file mode 100644 index 0000000..2ecda7a --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-null].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": null, + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean", + "type": "bool_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].1.json new file mode 100644 index 0000000..72f6434 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].1.json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "abc", + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean", + "type": "bool_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].json new file mode 100644 index 0000000..afbe769 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-random-text].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "abc", + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean, unable to interpret input", + "type": "bool_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-string].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-string].json new file mode 100644 index 0000000..4225300 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[bool-as-string].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "true", + "loc": [ + "body", + "bool1" + ], + "msg": "Input should be a valid boolean", + "type": "bool_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].1.json new file mode 100644 index 0000000..11c954d --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].1.json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "day value is outside expected range" + }, + "input": "2025-01-00", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date or datetime, day value is outside expected range", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].json new file mode 100644 index 0000000..11c954d --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month-00].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "day value is outside expected range" + }, + "input": "2025-01-00", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date or datetime, day value is outside expected range", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].1.json new file mode 100644 index 0000000..4fd9645 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].1.json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input is not of form YYYY-MM-DD" + }, + "input": "2025-01", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date, in RFC 3339 'full-date' format", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].json new file mode 100644 index 0000000..5339ebb --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-month].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input is too short" + }, + "input": "2025-01", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date or datetime, input is too short", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-day].json new file mode 100644 index 0000000..c2a9e35 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-day].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757548800, + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date in RFC 3339 'full-date' format, input is not a string", + "type": "date_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].1.json new file mode 100644 index 0000000..93c3b6c --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].1.json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757451600, + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date in RFC 3339 'full-date' format, input is not a string", + "type": "date_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].json new file mode 100644 index 0000000..ec72152 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset0].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 1757451600, + "loc": [ + "body", + "date1" + ], + "msg": "Datetimes provided to dates should have zero time - e.g. be exact dates", + "type": "date_from_datetime_inexact" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].1.json new file mode 100644 index 0000000..6221de9 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].1.json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input is not of form YYYY-MM-DD" + }, + "input": "1757451600", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date, in RFC 3339 'full-date' format", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].json new file mode 100644 index 0000000..a6b4ebe --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-offset1].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "1757451600", + "loc": [ + "body", + "date1" + ], + "msg": "Datetimes provided to dates should have zero time - e.g. be exact dates", + "type": "date_from_datetime_inexact" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-str-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-str-day].json new file mode 100644 index 0000000..9290d49 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[date-as-unixtimestamp-str-day].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input is not of form YYYY-MM-DD" + }, + "input": "1757548800", + "loc": [ + "body", + "date1" + ], + "msg": "Input should be a valid date, in RFC 3339 'full-date' format", + "type": "date_from_datetime_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-day].json new file mode 100644 index 0000000..a506372 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-day].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757548800, + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime in RFC 3339 format, input is not a string", + "type": "datetime_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-offset].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-offset].json new file mode 100644 index 0000000..6a938db --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-offset].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757451600, + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime in RFC 3339 format, input is not a string", + "type": "datetime_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-day].json new file mode 100644 index 0000000..8ea4803 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-day].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "1757548800", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-offset].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-offset].json new file mode 100644 index 0000000..7514cd6 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-as-unixtimestamp-str-offset].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "1757451600", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-t].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-t].json new file mode 100644 index 0000000..9812be1 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-t].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10t00:00:00Z", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-z].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-z].json new file mode 100644 index 0000000..05205e6 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-lower-case-z].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10T00:00:00z", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-naive].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-naive].json new file mode 100644 index 0000000..1346569 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-naive].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10T00:00:00", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-with-space].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-with-space].json new file mode 100644 index 0000000..ff10c6b --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[datetime-with-space].json @@ -0,0 +1,16 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10 00:00:00Z", + "loc": [ + "body", + "datetime1" + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[float-as-string].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[float-as-string].json new file mode 100644 index 0000000..57c69bd --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[float-as-string].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "50.2", + "loc": [ + "body", + "number1" + ], + "msg": "Input should be a valid number", + "type": "float_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-float].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-float].json new file mode 100644 index 0000000..708b56c --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-float].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 1.0, + "loc": [ + "body", + "number3" + ], + "msg": "Input should be a valid integer", + "type": "int_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-string].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-string].json new file mode 100644 index 0000000..fb5d1bd --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[int-as-string].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": "2", + "loc": [ + "body", + "number4" + ], + "msg": "Input should be a valid integer", + "type": "int_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-day].json new file mode 100644 index 0000000..ad8feb0 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-day].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757548800, + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime in RFC 3339 format, input is not a string", + "type": "datetime_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-offset].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-offset].json new file mode 100644 index 0000000..5947b49 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-offset].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input not string" + }, + "input": 1757451600, + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime in RFC 3339 format, input is not a string", + "type": "datetime_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-day].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-day].json new file mode 100644 index 0000000..3986f0c --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-day].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "1757548800", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-offset].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-offset].json new file mode 100644 index 0000000..3f5e7fe --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-as-unixtimestamp-str-offset].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "1757451600", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-t].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-t].json new file mode 100644 index 0000000..3367e5b --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-t].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10t00:00:00Z", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-z].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-z].json new file mode 100644 index 0000000..2b162da --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-lower-case-z].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10T00:00:00z", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-naive].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-naive].json new file mode 100644 index 0000000..02e65e7 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-naive].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10T00:00:00", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-with-space].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-with-space].json new file mode 100644 index 0000000..54e8d1b --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[list-dt-with-space].json @@ -0,0 +1,17 @@ +{ + "detail": [ + { + "ctx": { + "error": "input does not follow RFC 3339" + }, + "input": "2025-09-10 00:00:00Z", + "loc": [ + "body", + "listDatetime", + 0 + ], + "msg": "Input should be a valid datetime, in RFC 3339 format", + "type": "datetime_from_date_parsing" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].1.json new file mode 100644 index 0000000..e1f27d8 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].1.json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 123.1, + "loc": [ + "body", + "string1" + ], + "msg": "Input should be a valid string", + "type": "string_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].json new file mode 100644 index 0000000..e1f27d8 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-float].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 123.1, + "loc": [ + "body", + "string1" + ], + "msg": "Input should be a valid string", + "type": "string_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].1.json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].1.json new file mode 100644 index 0000000..93ac310 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].1.json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 123, + "loc": [ + "body", + "string1" + ], + "msg": "Input should be a valid string", + "type": "string_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].json b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].json new file mode 100644 index 0000000..93ac310 --- /dev/null +++ b/openapi_to_fastapi/tests/__snapshots__/test_router/test_validation[string-as-int].json @@ -0,0 +1,13 @@ +{ + "detail": [ + { + "input": 123, + "loc": [ + "body", + "string1" + ], + "msg": "Input should be a valid string", + "type": "string_type" + } + ] +} diff --git a/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.1.json b/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.1.json new file mode 100644 index 0000000..c1fa59d --- /dev/null +++ b/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.1.json @@ -0,0 +1,723 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Validation testing", + "description": "Definition for validating different kinds of fields", + "version": "0.1.0" + }, + "paths": { + "/TestValidation_v0.1": { + "post": { + "summary": "Validation testing", + "description": "Definition for validating different kinds of fields", + "operationId": "request_TestValidation_v0_1", + "parameters": [ + { + "name": "x-consent-token", + "in": "header", + "required": false, + "schema": { + "type": "string", + "description": "Optional consent token", + "default": "", + "title": "X-Consent-Token" + }, + "description": "Optional consent token" + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "type": "string", + "description": "The login token. Value should be \"Bearer [token]\"", + "default": "", + "title": "Authorization" + }, + "description": "The login token. Value should be \"Bearer [token]\"" + }, + { + "name": "x-authorization-provider", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The bare domain of the system that provided the token.", + "title": "X-Authorization-Provider" + }, + "description": "The bare domain of the system that provided the token." + }, + { + "name": "x-api-key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-API-Key", + "description": "API key or token" + }, + "description": "API key or token" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResponse" + } + } + } + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Unauthorized" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Forbidden" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "Not Found" + }, + "429": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitExceeded" + } + } + }, + "description": "Too Many Requests" + }, + "444": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataSourceNotFound" + } + } + }, + "description": "Additional Response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataSourceError" + } + } + }, + "description": "Internal Server Error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadGateway" + } + } + }, + "description": "Bad Gateway" + }, + "503": { + "content": { + "text/plain": {}, + "text/html": {}, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceUnavailable" + } + } + }, + "description": "Service Unavailable" + }, + "504": { + "content": { + "text/plain": {}, + "text/html": {}, + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayTimeout" + } + } + }, + "description": "Gateway Timeout" + }, + "550": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DoesNotConformToDefinition" + } + } + }, + "description": "Additional Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BadGateway": { + "properties": {}, + "type": "object", + "title": "BadGateway", + "description": "This response is reserved by Product Gateway." + }, + "DataSourceError": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "DataSourceError" + }, + "DataSourceNotFound": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "Data source not found" + } + }, + "type": "object", + "title": "DataSourceNotFound", + "description": "This response is reserved by Product Gateway." + }, + "DoesNotConformToDefinition": { + "properties": { + "message": { + "type": "string", + "title": "Message", + "default": "Response from data source does not conform to definition" + }, + "data_source_status_code": { + "type": "integer", + "title": "Data source status code", + "description": "HTTP status code returned from the data source" + } + }, + "type": "object", + "required": ["data_source_status_code"], + "title": "DoesNotConformToDefinition", + "description": "This response is reserved by Product Gateway." + }, + "ExampleEnum": { + "type": "string", + "enum": ["foo", "bar"], + "title": "ExampleEnum" + }, + "Forbidden": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "Forbidden" + }, + "GatewayTimeout": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "" + } + }, + "type": "object", + "title": "GatewayTimeout", + "description": "This response is reserved by Product Gateway." + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "NotFound": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "NotFound" + }, + "RateLimitExceeded": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "Rate limit exceeded" + } + }, + "type": "object", + "title": "RateLimitExceeded", + "description": "This response is reserved by Product Gateway." + }, + "ServiceUnavailable": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "" + } + }, + "type": "object", + "title": "ServiceUnavailable", + "description": "This response is reserved by Product Gateway." + }, + "Unauthorized": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "Unauthorized" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError" + }, + "ValidationRequest": { + "properties": { + "number1": { + "type": "number", + "title": "Number 1", + "description": "Float with no extra validation.", + "examples": [50.5] + }, + "number2": { + "type": "number", + "maximum": 100.0, + "minimum": 0.0, + "title": "Number 2", + "description": "Float 0-100.", + "examples": [50.5] + }, + "number3": { + "type": "integer", + "title": "Number 3", + "description": "Integer with no extra validation.", + "examples": [50] + }, + "number4": { + "type": "integer", + "maximum": 10.0, + "minimum": 0.0, + "title": "Number 4", + "description": "Integer 0-10.", + "examples": [5] + }, + "number5": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Number 5", + "description": "Optional float with no extra validation.", + "examples": [50.2] + }, + "number6": { + "anyOf": [ + { + "type": "number", + "maximum": 100.0, + "minimum": -0.0 + }, + { + "type": "null" + } + ], + "title": "Number 6", + "description": "Optional float 0-100.", + "examples": [50.3] + }, + "number7": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Number 7", + "description": "Optional integer with no extra validation.", + "examples": [50] + }, + "number8": { + "anyOf": [ + { + "type": "integer", + "maximum": 10.0, + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Number 8", + "description": "Optional integer 0-10.", + "examples": [5] + }, + "bool1": { + "type": "boolean", + "title": "Bool 1", + "description": "Required boolean.", + "examples": [true] + }, + "bool2": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Bool 2", + "description": "Optional boolean.", + "examples": [true] + }, + "string1": { + "type": "string", + "title": "String 1", + "description": "Required string.", + "examples": ["Foo"] + }, + "string2": { + "type": "string", + "maxLength": 10, + "title": "String 2", + "description": "Required string with max length 10.", + "examples": ["Foo"] + }, + "string3": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "String 3", + "description": "Optional string.", + "examples": ["Foo"] + }, + "string4": { + "anyOf": [ + { + "type": "string", + "maxLength": 10, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "String 4", + "description": "Optional string with min length 1 and max length 10.", + "examples": ["Foo"] + }, + "date1": { + "type": "string", + "format": "date", + "title": "Date 1", + "description": "Required date.", + "examples": ["2025-01-01"] + }, + "date2": { + "anyOf": [ + { + "type": "string", + "format": "date" + }, + { + "type": "null" + } + ], + "title": "Date 2", + "description": "Optional date.", + "examples": ["2025-01-01"] + }, + "datetime1": { + "type": "string", + "format": "date-time", + "title": "Datetime 1", + "description": "Required datetime.", + "examples": ["2025-01-01T00:00:00Z"] + }, + "datetime2": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Datetime 2", + "description": "Optional datetime.", + "examples": ["2025-01-01T00:00:00Z"] + }, + "enum1": { + "$ref": "#/components/schemas/ExampleEnum", + "title": "Enum 1", + "description": "Required enum.", + "examples": ["foo"] + }, + "enum2": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExampleEnum" + }, + { + "type": "null" + } + ], + "title": "Enum 2", + "description": "Optional enum.", + "examples": ["bar"] + }, + "listStr": { + "items": { + "type": "string" + }, + "type": "array", + "title": "List strings", + "description": "List of string.", + "examples": [["abc", "def"]] + }, + "listFloat": { + "items": { + "type": "number" + }, + "type": "array", + "title": "List floats", + "description": "List of floats.", + "examples": [[0.2, 0.5]] + }, + "listInt": { + "items": { + "type": "number" + }, + "type": "array", + "title": "List ints", + "description": "List of integers.", + "examples": [[1, 2, 3]] + }, + "listDate": { + "items": { + "type": "string", + "format": "date" + }, + "type": "array", + "title": "List dates", + "description": "List of integers.", + "examples": [["2025-01-01"]] + }, + "listDatetime": { + "items": { + "type": "string", + "format": "date-time" + }, + "type": "array", + "title": "List datetimes", + "description": "List of datetimes.", + "examples": [["2025-01-01T00:00:00Z"]] + }, + "listBool": { + "items": { + "type": "boolean" + }, + "type": "array", + "title": "List booleans", + "description": "List of booleans.", + "examples": [[true, false]] + }, + "listEnum": { + "items": { + "$ref": "#/components/schemas/ExampleEnum" + }, + "type": "array", + "title": "List enums", + "description": "List of booleans.", + "examples": [["foo", "bar"]] + } + }, + "type": "object", + "required": [ + "number1", + "number2", + "number3", + "number4", + "bool1", + "string1", + "string2", + "date1", + "datetime1", + "enum1", + "listStr", + "listFloat", + "listInt", + "listDate", + "listDatetime", + "listBool", + "listEnum" + ], + "title": "ValidationRequest" + }, + "ValidationResponse": { + "properties": { + "ok": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "OK", + "examples": [true] + } + }, + "type": "object", + "title": "ValidationResponse" + } + } + } +} diff --git a/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.2.json b/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.2.json new file mode 100644 index 0000000..042e21d --- /dev/null +++ b/openapi_to_fastapi/tests/data/definitions/TestValidation_v0.2.json @@ -0,0 +1,724 @@ +{ + "openapi": "3.1.0", + "x-strict-validation": true, + "info": { + "title": "Validation testing", + "description": "Definition for validating different kinds of fields", + "version": "0.2.0" + }, + "paths": { + "/TestValidation_v0.2": { + "post": { + "summary": "Validation testing", + "description": "Definition for validating different kinds of fields", + "operationId": "request_TestValidation_v0_1", + "parameters": [ + { + "name": "x-consent-token", + "in": "header", + "required": false, + "schema": { + "type": "string", + "description": "Optional consent token", + "default": "", + "title": "X-Consent-Token" + }, + "description": "Optional consent token" + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "type": "string", + "description": "The login token. Value should be \"Bearer [token]\"", + "default": "", + "title": "Authorization" + }, + "description": "The login token. Value should be \"Bearer [token]\"" + }, + { + "name": "x-authorization-provider", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The bare domain of the system that provided the token.", + "title": "X-Authorization-Provider" + }, + "description": "The bare domain of the system that provided the token." + }, + { + "name": "x-api-key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-API-Key", + "description": "API key or token" + }, + "description": "API key or token" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResponse" + } + } + } + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Unauthorized" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Forbidden" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "Not Found" + }, + "429": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitExceeded" + } + } + }, + "description": "Too Many Requests" + }, + "444": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataSourceNotFound" + } + } + }, + "description": "Additional Response" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataSourceError" + } + } + }, + "description": "Internal Server Error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadGateway" + } + } + }, + "description": "Bad Gateway" + }, + "503": { + "content": { + "text/plain": {}, + "text/html": {}, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceUnavailable" + } + } + }, + "description": "Service Unavailable" + }, + "504": { + "content": { + "text/plain": {}, + "text/html": {}, + "application/json": { + "schema": { + "$ref": "#/components/schemas/GatewayTimeout" + } + } + }, + "description": "Gateway Timeout" + }, + "550": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DoesNotConformToDefinition" + } + } + }, + "description": "Additional Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BadGateway": { + "properties": {}, + "type": "object", + "title": "BadGateway", + "description": "This response is reserved by Product Gateway." + }, + "DataSourceError": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "DataSourceError" + }, + "DataSourceNotFound": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "Data source not found" + } + }, + "type": "object", + "title": "DataSourceNotFound", + "description": "This response is reserved by Product Gateway." + }, + "DoesNotConformToDefinition": { + "properties": { + "message": { + "type": "string", + "title": "Message", + "default": "Response from data source does not conform to definition" + }, + "data_source_status_code": { + "type": "integer", + "title": "Data source status code", + "description": "HTTP status code returned from the data source" + } + }, + "type": "object", + "required": ["data_source_status_code"], + "title": "DoesNotConformToDefinition", + "description": "This response is reserved by Product Gateway." + }, + "ExampleEnum": { + "type": "string", + "enum": ["foo", "bar"], + "title": "ExampleEnum" + }, + "Forbidden": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "Forbidden" + }, + "GatewayTimeout": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "" + } + }, + "type": "object", + "title": "GatewayTimeout", + "description": "This response is reserved by Product Gateway." + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "NotFound": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "NotFound" + }, + "RateLimitExceeded": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "Rate limit exceeded" + } + }, + "type": "object", + "title": "RateLimitExceeded", + "description": "This response is reserved by Product Gateway." + }, + "ServiceUnavailable": { + "properties": { + "message": { + "type": "string", + "title": "Error message", + "description": "Error description", + "default": "" + } + }, + "type": "object", + "title": "ServiceUnavailable", + "description": "This response is reserved by Product Gateway." + }, + "Unauthorized": { + "properties": { + "type": { + "type": "string", + "title": "Error type", + "description": "Error identifier" + }, + "message": { + "type": "string", + "title": "Error message", + "description": "Error description" + } + }, + "type": "object", + "required": ["type", "message"], + "title": "Unauthorized" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError" + }, + "ValidationRequest": { + "properties": { + "number1": { + "type": "number", + "title": "Number 1", + "description": "Float with no extra validation.", + "examples": [50.5] + }, + "number2": { + "type": "number", + "maximum": 100.0, + "minimum": 0.0, + "title": "Number 2", + "description": "Float 0-100.", + "examples": [50.5] + }, + "number3": { + "type": "integer", + "title": "Number 3", + "description": "Integer with no extra validation.", + "examples": [50] + }, + "number4": { + "type": "integer", + "maximum": 10.0, + "minimum": 0.0, + "title": "Number 4", + "description": "Integer 0-10.", + "examples": [5] + }, + "number5": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Number 5", + "description": "Optional float with no extra validation.", + "examples": [50.2] + }, + "number6": { + "anyOf": [ + { + "type": "number", + "maximum": 100.0, + "minimum": -0.0 + }, + { + "type": "null" + } + ], + "title": "Number 6", + "description": "Optional float 0-100.", + "examples": [50.3] + }, + "number7": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Number 7", + "description": "Optional integer with no extra validation.", + "examples": [50] + }, + "number8": { + "anyOf": [ + { + "type": "integer", + "maximum": 10.0, + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Number 8", + "description": "Optional integer 0-10.", + "examples": [5] + }, + "bool1": { + "type": "boolean", + "title": "Bool 1", + "description": "Required boolean.", + "examples": [true] + }, + "bool2": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Bool 2", + "description": "Optional boolean.", + "examples": [true] + }, + "string1": { + "type": "string", + "title": "String 1", + "description": "Required string.", + "examples": ["Foo"] + }, + "string2": { + "type": "string", + "maxLength": 10, + "title": "String 2", + "description": "Required string with max length 10.", + "examples": ["Foo"] + }, + "string3": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "String 3", + "description": "Optional string.", + "examples": ["Foo"] + }, + "string4": { + "anyOf": [ + { + "type": "string", + "maxLength": 10, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "String 4", + "description": "Optional string with min length 1 and max length 10.", + "examples": ["Foo"] + }, + "date1": { + "type": "string", + "format": "date", + "title": "Date 1", + "description": "Required date.", + "examples": ["2025-01-01"] + }, + "date2": { + "anyOf": [ + { + "type": "string", + "format": "date" + }, + { + "type": "null" + } + ], + "title": "Date 2", + "description": "Optional date.", + "examples": ["2025-01-01"] + }, + "datetime1": { + "type": "string", + "format": "date-time", + "title": "Datetime 1", + "description": "Required datetime.", + "examples": ["2025-01-01T00:00:00Z"] + }, + "datetime2": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Datetime 2", + "description": "Optional datetime.", + "examples": ["2025-01-01T00:00:00Z"] + }, + "enum1": { + "$ref": "#/components/schemas/ExampleEnum", + "title": "Enum 1", + "description": "Required enum.", + "examples": ["foo"] + }, + "enum2": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExampleEnum" + }, + { + "type": "null" + } + ], + "title": "Enum 2", + "description": "Optional enum.", + "examples": ["bar"] + }, + "listStr": { + "items": { + "type": "string" + }, + "type": "array", + "title": "List strings", + "description": "List of string.", + "examples": [["abc", "def"]] + }, + "listFloat": { + "items": { + "type": "number" + }, + "type": "array", + "title": "List floats", + "description": "List of floats.", + "examples": [[0.2, 0.5]] + }, + "listInt": { + "items": { + "type": "number" + }, + "type": "array", + "title": "List ints", + "description": "List of integers.", + "examples": [[1, 2, 3]] + }, + "listDate": { + "items": { + "type": "string", + "format": "date" + }, + "type": "array", + "title": "List dates", + "description": "List of integers.", + "examples": [["2025-01-01"]] + }, + "listDatetime": { + "items": { + "type": "string", + "format": "date-time" + }, + "type": "array", + "title": "List datetimes", + "description": "List of datetimes.", + "examples": [["2025-01-01T00:00:00Z"]] + }, + "listBool": { + "items": { + "type": "boolean" + }, + "type": "array", + "title": "List booleans", + "description": "List of booleans.", + "examples": [[true, false]] + }, + "listEnum": { + "items": { + "$ref": "#/components/schemas/ExampleEnum" + }, + "type": "array", + "title": "List enums", + "description": "List of booleans.", + "examples": [["foo", "bar"]] + } + }, + "type": "object", + "required": [ + "number1", + "number2", + "number3", + "number4", + "bool1", + "string1", + "string2", + "date1", + "datetime1", + "enum1", + "listStr", + "listFloat", + "listInt", + "listDate", + "listDatetime", + "listBool", + "listEnum" + ], + "title": "ValidationRequest" + }, + "ValidationResponse": { + "properties": { + "ok": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "OK", + "examples": [true] + } + }, + "type": "object", + "title": "ValidationResponse" + } + } + } +} diff --git a/openapi_to_fastapi/tests/data/src/README.md b/openapi_to_fastapi/tests/data/src/README.md new file mode 100644 index 0000000..4cc1f51 --- /dev/null +++ b/openapi_to_fastapi/tests/data/src/README.md @@ -0,0 +1,9 @@ +# README + +This `src` folder contains source files from which some of the definitions have once +upon a time been generated using the +[ioxio-data-product-definition-tooling](https://github.com/ioxio-dataspace/ioxio-data-product-definition-tooling). + +They might be used later as a base to update or change the definitions, but please note +that the definitions (`.json` files) might have been updated separately or the tooling +might have been updated and might not thus generate compatible definitions. diff --git a/openapi_to_fastapi/tests/data/src/TestValidation_v0.1.py b/openapi_to_fastapi/tests/data/src/TestValidation_v0.1.py new file mode 100644 index 0000000..56f0ac6 --- /dev/null +++ b/openapi_to_fastapi/tests/data/src/TestValidation_v0.1.py @@ -0,0 +1,209 @@ +from datetime import date, datetime +from enum import Enum +from typing import List, Optional + +from definition_tooling.converter import CamelCaseModel, DataProductDefinition +from pydantic import Field + + +class ExampleEnum(str, Enum): + FOO = "foo" + BAR = "bar" + + +class ValidationBase(CamelCaseModel): + number_1: float = Field( + ..., + title="Number 1", + description="Float with no extra validation.", + examples=[50.5], + ) + number_2: float = Field( + ..., + title="Number 2", + description="Float 0-100.", + ge=0.0, + le=100.0, + examples=[50.5], + ) + number_3: int = Field( + ..., + title="Number 3", + description="Integer with no extra validation.", + examples=[50], + ) + number_4: int = Field( + ..., + title="Number 4", + description="Integer 0-10.", + ge=0, + le=10, + examples=[5], + ) + number_5: Optional[float] = Field( + None, + title="Number 5", + description="Optional float with no extra validation.", + examples=[50.2], + ) + number_6: Optional[float] = Field( + None, + title="Number 6", + description="Optional float 0-100.", + ge=-0.0, + le=100.0, + examples=[50.3], + ) + number_7: Optional[int] = Field( + None, + title="Number 7", + description="Optional integer with no extra validation.", + examples=[50], + ) + number_8: Optional[int] = Field( + None, + title="Number 8", + description="Optional integer 0-10.", + ge=0, + le=10, + examples=[5], + ) + bool_1: bool = Field( + ..., + title="Bool 1", + description="Required boolean.", + examples=[True], + ) + bool_2: Optional[bool] = Field( + None, + title="Bool 2", + description="Optional boolean.", + examples=[True], + ) + string_1: str = Field( + ..., + title="String 1", + description="Required string.", + examples=["Foo"], + ) + string_2: str = Field( + ..., + title="String 2", + max_length=10, + description="Required string with max length 10.", + examples=["Foo"], + ) + string_3: Optional[str] = Field( + None, + title="String 3", + description="Optional string.", + examples=["Foo"], + ) + string_4: Optional[str] = Field( + None, + title="String 4", + min_length=1, + max_length=10, + description="Optional string with min length 1 and max length 10.", + examples=["Foo"], + ) + date_1: date = Field( + ..., + title="Date 1", + description="Required date.", + examples=[date.fromisoformat("2025-01-01")], + ) + date_2: Optional[date] = Field( + None, + title="Date 2", + description="Optional date.", + examples=[date.fromisoformat("2025-01-01")], + ) + datetime_1: datetime = Field( + ..., + title="Datetime 1", + description="Required datetime.", + examples=[datetime.fromisoformat("2025-01-01T00:00:00+00:00")], + ) + datetime_2: Optional[datetime] = Field( + None, + title="Datetime 2", + description="Optional datetime.", + examples=[datetime.fromisoformat("2025-01-01T00:00:00+00:00")], + ) + enum_1: ExampleEnum = Field( + ..., + title="Enum 1", + description="Required enum.", + examples=[ExampleEnum.FOO], + ) + enum_2: Optional[ExampleEnum] = Field( + None, + title="Enum 2", + description="Optional enum.", + examples=[ExampleEnum.BAR], + ) + list_str: List[str] = Field( + ..., + title="List strings", + description="List of string.", + examples=[["abc", "def"]], + ) + list_float: List[float] = Field( + ..., + title="List floats", + description="List of floats.", + examples=[[0.2, 0.5]], + ) + list_int: List[float] = Field( + ..., + title="List ints", + description="List of integers.", + examples=[[1, 2, 3]], + ) + list_date: List[date] = Field( + ..., + title="List dates", + description="List of integers.", + examples=[[date.fromisoformat("2025-01-01")]], + ) + list_datetime: List[datetime] = Field( + ..., + title="List datetimes", + description="List of datetimes.", + examples=[[datetime.fromisoformat("2025-01-01T00:00:00+00:00")]], + ) + list_bool: List[bool] = Field( + ..., + title="List booleans", + description="List of booleans.", + examples=[[True, False]], + ) + list_enum: List[ExampleEnum] = Field( + ..., + title="List enums", + description="List of booleans.", + examples=[[ExampleEnum.FOO, ExampleEnum.BAR]], + ) + + +class ValidationRequest(ValidationBase): + pass + + +class ValidationResponse(CamelCaseModel): + ok: Optional[bool] = Field( + None, + title="OK", + examples=[True], + ) + + +DEFINITION = DataProductDefinition( + version="0.1.0", + title="Validation testing", + description="Definition for validating different kinds of fields", + request=ValidationRequest, + response=ValidationResponse, + strict_validation=False, +) diff --git a/openapi_to_fastapi/tests/test_router.py b/openapi_to_fastapi/tests/test_router.py index e9dfce7..8744f61 100644 --- a/openapi_to_fastapi/tests/test_router.py +++ b/openapi_to_fastapi/tests/test_router.py @@ -1,3 +1,5 @@ +from typing import Any, Dict + import pydantic import pytest from fastapi import Depends, Header, HTTPException, Request @@ -285,3 +287,312 @@ def weather_metric(request): "/Company/BasicInfo", json={"companyId": "test"}, headers={"X-Brew": "coffee"} ) assert resp.status_code == 418, resp.json() + + +@pytest.mark.parametrize( + ["overrides", "expected_lax_code", "expected_strict_code"], + [ + pytest.param( + {}, + 200, + 200, + id="default", + ), + pytest.param( + {"number1": "50.2"}, + 200, + 422, + id="float-as-string", + ), + pytest.param( + {"number4": "2"}, + 200, + 422, + id="int-as-string", + ), + pytest.param( + {"number1": 1}, + 200, + 200, + id="float-as-int", + ), + pytest.param( + {"number3": 1.00}, + 200, + 422, + id="int-as-float", + ), + pytest.param( + {"bool1": "true"}, + 200, + 422, + id="bool-as-string", + ), + pytest.param( + {"bool1": "abc"}, + 422, + 422, + id="bool-as-random-text", + ), + pytest.param( + {"bool1": 1}, + 200, + 422, + id="bool-as-int", + ), + pytest.param( + {"bool1": None}, + 422, + 422, + id="bool-as-null", + ), + pytest.param( + {"string1": 123}, + 422, + 422, + id="string-as-int", + ), + pytest.param( + {"string1": 123.1}, + 422, + 422, + id="string-as-float", + ), + pytest.param( + {"date1": 1757451600}, + 422, + 422, + id="date-as-unixtimestamp-offset", + ), + pytest.param( + {"date1": 1757548800}, + 200, + 422, + id="date-as-unixtimestamp-day", + ), + pytest.param( + {"date1": "1757451600"}, + 422, + 422, + id="date-as-unixtimestamp-offset", + ), + pytest.param( + {"date1": "1757548800"}, + 200, + 422, + id="date-as-unixtimestamp-str-day", + ), + pytest.param( + {"date1": "2025-01"}, + 422, + 422, + id="date-as-month", + ), + pytest.param( + {"date1": "2025-01-00"}, + 422, + 422, + id="date-as-month-00", + ), + pytest.param( + {"datetime1": 1757451600}, + 200, + 422, + id="datetime-as-unixtimestamp-offset", + ), + pytest.param( + {"datetime1": 1757548800}, + 200, + 422, + id="datetime-as-unixtimestamp-day", + ), + pytest.param( + {"datetime1": "1757451600"}, + 200, + 422, + id="datetime-as-unixtimestamp-str-offset", + ), + pytest.param( + {"datetime1": "1757548800"}, + 200, + 422, + id="datetime-as-unixtimestamp-str-day", + ), + pytest.param( + {"datetime1": "2025-09-10T00:00:00"}, + 200, + 422, + id="datetime-naive", + ), + pytest.param( + {"datetime1": "2025-09-10t00:00:00Z"}, + 200, + 422, + id="datetime-lower-case-t", + ), + pytest.param( + {"datetime1": "2025-09-10T00:00:00z"}, + 200, + 422, + id="datetime-lower-case-z", + ), + pytest.param( + {"datetime1": "2025-09-10 00:00:00Z"}, + 200, + 422, + id="datetime-with-space", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56+10:00"}, + 200, + 200, + id="datetime-with-plus", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56-10:00"}, + 200, + 200, + id="datetime-with-minus", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56Z"}, + 200, + 200, + id="datetime-with-z", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56.789+10:00"}, + 200, + 200, + id="datetime-with-plus-secfrac", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56.789-10:00"}, + 200, + 200, + id="datetime-with-minus-secfrac", + ), + pytest.param( + {"datetime1": "2025-09-10T12:34:56.789Z"}, + 200, + 200, + id="datetime-with-z-secfrac", + ), + pytest.param( + {"listDatetime": [1757451600]}, + 200, + 422, + id="list-dt-as-unixtimestamp-offset", + ), + pytest.param( + {"listDatetime": [1757548800]}, + 200, + 422, + id="list-dt-as-unixtimestamp-day", + ), + pytest.param( + {"listDatetime": ["1757451600"]}, + 200, + 422, + id="list-dt-as-unixtimestamp-str-offset", + ), + pytest.param( + {"listDatetime": ["1757548800"]}, + 200, + 422, + id="list-dt-as-unixtimestamp-str-day", + ), + pytest.param( + {"listDatetime": ["2025-09-10T00:00:00"]}, + 200, + 422, + id="list-dt-naive", + ), + pytest.param( + {"listDatetime": ["2025-09-10t00:00:00Z"]}, + 200, + 422, + id="list-dt-lower-case-t", + ), + pytest.param( + {"listDatetime": ["2025-09-10T00:00:00z"]}, + 200, + 422, + id="list-dt-lower-case-z", + ), + pytest.param( + {"listDatetime": ["2025-09-10 00:00:00Z"]}, + 200, + 422, + id="list-dt-with-space", + ), + ], +) +def test_validation( + app, + client, + specs_root, + json_snapshot, + overrides, + expected_lax_code, + expected_strict_code, +): + + spec_router = SpecRouter(specs_root / "definitions") + app.include_router(spec_router.to_fastapi_router()) + + @spec_router.post("/TestValidation_v0.1") + def lax_validation_route(request): + return {"ok": True} + + @spec_router.post("/TestValidation_v0.2") + def strict_validation_route(request): + return {"ok": True} + + def make_lax_request(json: Dict[str, Any]) -> Any: + return client.post("/TestValidation_v0.1", json=json) + + def make_strict_request(json: Dict[str, Any]) -> Any: + return client.post("/TestValidation_v0.2", json=json) + + valid_data = { + "number1": 50.5, + "number2": 50.5, + "number3": 50, + "number4": 5, + "number5": 50.2, + "number6": 50.3, + "number7": 50, + "number8": 5, + "bool1": True, + "bool2": True, + "string1": "Foo", + "string2": "Foo", + "string3": "Foo", + "string4": "Foo", + "date1": "2025-01-01", + "date2": "2025-01-01", + "datetime1": "2025-01-01T00:00:00+00:00", + "datetime2": "2025-01-01T00:00:00+00:00", + "enum1": "foo", + "enum2": "foo", + "listStr": ["abc", "def"], + "listFloat": [0.2, 0.5], + "listInt": [1, 2, 3], + "listDate": ["2025-01-01"], + "listDatetime": ["2025-01-01T00:00:00+00:00"], + "listBool": [True, False], + "listEnum": ["foo", "bar"], + } + + data = {**valid_data, **overrides} + + resp = make_lax_request(json=data) + assert resp.status_code == expected_lax_code, resp.json() + if resp.status_code != 200: + assert json_snapshot == resp.json() + + resp = make_strict_request(json=data) + assert resp.status_code == expected_strict_code, resp.json() + if resp.status_code != 200: + assert json_snapshot == resp.json() diff --git a/poetry.lock b/poetry.lock index 2e71e2f..48903ce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -148,13 +148,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "datamodel-code-generator" -version = "0.32.0" +version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" files = [ - {file = "datamodel_code_generator-0.32.0-py3-none-any.whl", hash = "sha256:48f3cabbb792398112ee756b23a319e17b001ee534896b324893a98ff10e0a55"}, - {file = "datamodel_code_generator-0.32.0.tar.gz", hash = "sha256:c6f84a6a7683ef9841940b0931aa1ee338b19950ba5b10c920f9c7ad6f5e5b72"}, + {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, + {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, ] [package.dependencies] @@ -634,19 +634,19 @@ files = [ [[package]] name = "pydantic" -version = "2.11.1" +version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8"}, - {file = "pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] annotated-types = ">=0.6.0" email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} -pydantic-core = "2.33.0" +pydantic-core = "2.33.2" typing-extensions = ">=4.12.2" typing-inspection = ">=0.4.0" @@ -656,110 +656,110 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.33.0" +version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e"}, - {file = "pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5"}, - {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276"}, - {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760"}, - {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa"}, - {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c"}, - {file = "pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025"}, - {file = "pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc"}, - {file = "pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef"}, - {file = "pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd"}, - {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453"}, - {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b"}, - {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915"}, - {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2"}, - {file = "pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86"}, - {file = "pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b"}, - {file = "pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a"}, - {file = "pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43"}, - {file = "pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862"}, - {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a"}, - {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099"}, - {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6"}, - {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3"}, - {file = "pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2"}, - {file = "pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48"}, - {file = "pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6"}, - {file = "pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555"}, - {file = "pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9"}, - {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181"}, - {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d"}, - {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3"}, - {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b"}, - {file = "pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585"}, - {file = "pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606"}, - {file = "pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225"}, - {file = "pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87"}, - {file = "pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b"}, - {file = "pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7"}, - {file = "pydantic_core-2.33.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7c9c84749f5787781c1c45bb99f433402e484e515b40675a5d121ea14711cf61"}, - {file = "pydantic_core-2.33.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:64672fa888595a959cfeff957a654e947e65bbe1d7d82f550417cbd6898a1d6b"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bc7367c0961dec292244ef2549afa396e72e28cc24706210bd44d947582c59"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce72d46eb201ca43994303025bd54d8a35a3fc2a3495fac653d6eb7205ce04f4"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14229c1504287533dbf6b1fc56f752ce2b4e9694022ae7509631ce346158de11"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:085d8985b1c1e48ef271e98a658f562f29d89bda98bf120502283efbc87313eb"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31860fbda80d8f6828e84b4a4d129fd9c4535996b8249cfb8c720dc2a1a00bb8"}, - {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f200b2f20856b5a6c3a35f0d4e344019f805e363416e609e9b47c552d35fd5ea"}, - {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f72914cfd1d0176e58ddc05c7a47674ef4222c8253bf70322923e73e14a4ac3"}, - {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91301a0980a1d4530d4ba7e6a739ca1a6b31341252cb709948e0aca0860ce0ae"}, - {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7419241e17c7fbe5074ba79143d5523270e04f86f1b3a0dff8df490f84c8273a"}, - {file = "pydantic_core-2.33.0-cp39-cp39-win32.whl", hash = "sha256:7a25493320203005d2a4dac76d1b7d953cb49bce6d459d9ae38e30dd9f29bc9c"}, - {file = "pydantic_core-2.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:82a4eba92b7ca8af1b7d5ef5f3d9647eee94d1f74d21ca7c21e3a2b92e008358"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330"}, - {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936"}, - {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba95691cf25f63df53c1d342413b41bd7762d9acb425df8858d7efa616c0870e"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f1ab031feb8676f6bd7c85abec86e2935850bf19b84432c64e3e239bffeb1ec"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1151827eef98b83d49b6ca6065575876a02d2211f259fb1a6b7757bd24dd8"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d931ea2c1464b738ace44b7334ab32a2fd50be023d863935eb00f42be1778"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bcf0bab28995d483f6c8d7db25e0d05c3efa5cebfd7f56474359e7137f39856"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:89670d7a0045acb52be0566df5bc8b114ac967c662c06cf5e0c606e4aadc964b"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b716294e721d8060908dbebe32639b01bfe61b15f9f57bcc18ca9a0e00d9520b"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fc53e05c16697ff0c1c7c2b98e45e131d4bfb78068fffff92a82d169cbb4c7b7"}, - {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:68504959253303d3ae9406b634997a2123a0b0c1da86459abbd0ffc921695eac"}, - {file = "pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] @@ -1000,4 +1000,4 @@ typing-extensions = ">=4.12.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1a0c063e7377183eb18c0304b506b5086dafa453eaf7aeab59c5f50af99dcd51" +content-hash = "ea6052ce4672ff2a8a6376feb84a837841798edccac770e7855d3b4bf0a79b13" diff --git a/pyproject.toml b/pyproject.toml index 824dc9c..3b3f4f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openapi-to-fastapi" -version = "0.19.0" +version = "0.20.0" description = "Create FastAPI routes from OpenAPI spec" authors = ["IOXIO Ltd"] license = "BSD-3-Clause" @@ -15,11 +15,11 @@ openapi-validator = "openapi_to_fastapi.cli:cli_validate_specs" [tool.poetry.dependencies] python = "^3.9" -datamodel-code-generator = "^0.32.0" +datamodel-code-generator = "^0.33.0" fastapi = "^0.116.1" click = "^8.1.8" coloredlogs = "^15.0.1" -pydantic = {version = "^2.11.1", extras = ["email"]} +pydantic = {version = "^2.11.7", extras = ["email"]} [tool.poetry.group.dev.dependencies] isort = "^5.13.2"