From 5df9feb594be8f6865315804ea82ec6cdcac0a3c Mon Sep 17 00:00:00 2001 From: ThElias Date: Mon, 9 Oct 2023 12:14:07 +0100 Subject: [PATCH 01/15] feat: upgrade pydantic to 2.4.2 --- definition_tooling/converter/converter.py | 36 +-- .../tests/data/AirQuality/Current.py | 10 +- .../tests/data/Appliance/CoffeeBrewer.py | 8 +- .../converter/tests/data/Company/BasicInfo.py | 10 +- .../tests/data/Weather/Current/Metric.py | 18 +- log.txt | 0 poetry.lock | 236 +++++++++++------- pyproject.toml | 4 +- 8 files changed, 195 insertions(+), 127 deletions(-) create mode 100644 log.txt diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index f05baa8..71f1445 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -6,18 +6,19 @@ from deepdiff import DeepDiff from fastapi import FastAPI, Header -from pydantic import BaseModel, ValidationError, conint, validator +from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator +from pydantic.json_schema import JsonSchemaValue +from pydantic_core import core_schema from rich import print from semver import Version from stringcase import camelcase +from typing_extensions import Annotated from definition_tooling.api_errors import DATA_PRODUCT_ERRORS class CamelCaseModel(BaseModel): - class Config: - alias_generator = camelcase - allow_population_by_field_name = True + model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) class ErrorModel(BaseModel): @@ -63,7 +64,7 @@ def __call__(self, model_cls: Type[BaseModel]) -> ErrorModel: ) -ERROR_CODE = conint(ge=400, lt=600) +ERROR_CODE = Annotated[int, Field(ge=400, lt=600)] class PydanticVersion(Version): @@ -80,20 +81,20 @@ def _parse(cls, version): return cls.parse(version) @classmethod - def __get_validators__(cls): + def __get_pydantic_core_schema__( + cls, source_type, _handler + ) -> core_schema.CoreSchema: """Return a list of validator methods for pydantic models.""" - yield cls._parse + return core_schema.no_info_wrap_validator_function( + cls.parse, + core_schema.str_schema(), + serialization=core_schema.to_string_ser_schema(), + ) @classmethod - def __modify_schema__(cls, field_schema): + def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue: """Inject/mutate the pydantic field schema in-place.""" - field_schema.update( - examples=[ - "1.0.2", - "2.15.3-alpha", - "21.3.15-beta+12345", - ] - ) + return handler(core_schema.str_schema()) class DataProductDefinition(BaseModel): @@ -101,14 +102,15 @@ class DataProductDefinition(BaseModel): deprecated: bool = False description: str error_responses: Dict[ERROR_CODE, ErrorModel] = {} - name: Optional[str] + name: Optional[str] = None request: Type[BaseModel] requires_authorization: bool = False requires_consent: bool = False response: Type[BaseModel] title: str - @validator("error_responses") + @field_validator("error_responses") + @classmethod def validate_error_responses(cls, v: Dict[ERROR_CODE, ErrorModel]): status_codes = set(v.keys()) reserved_status_codes = set(DATA_PRODUCT_ERRORS.keys()) diff --git a/definition_tooling/converter/tests/data/AirQuality/Current.py b/definition_tooling/converter/tests/data/AirQuality/Current.py index 66ba9e5..341f5f3 100644 --- a/definition_tooling/converter/tests/data/AirQuality/Current.py +++ b/definition_tooling/converter/tests/data/AirQuality/Current.py @@ -10,7 +10,7 @@ class CurrentAirQualityRequest(CamelCaseModel): ..., title="Latitude", description="The latitude coordinate of the desired location", - example=60.192059, + examples=[60.192059], ge=-90, le=90, ) @@ -18,7 +18,7 @@ class CurrentAirQualityRequest(CamelCaseModel): ..., title="Longitude", description="The longitude coordinate of the desired location", - example=24.945831, + examples=[24.945831], ge=-180, le=180, ) @@ -34,19 +34,19 @@ class CurrentAirQualityResponse(CamelCaseModel): "201-300 Very Unhealthy;\n301+ Hazardous" ), ge=0, - example=30, + examples=[30], ) timestamp: str = Field( ..., title="Timestamp", description="Current timestamp in RFC 3339 format", - example="2020-04-03T13:00:00Z", + examples=["2020-04-03T13:00:00Z"], ) attribution: List[str] = Field( ..., title="Source Attribution", description="List of text to show required credits to data sources", - example=["Moscow State environmental monitoring"], + examples=[["Moscow State environmental monitoring"]], ) diff --git a/definition_tooling/converter/tests/data/Appliance/CoffeeBrewer.py b/definition_tooling/converter/tests/data/Appliance/CoffeeBrewer.py index 9a1da55..8acd1b9 100644 --- a/definition_tooling/converter/tests/data/Appliance/CoffeeBrewer.py +++ b/definition_tooling/converter/tests/data/Appliance/CoffeeBrewer.py @@ -12,7 +12,7 @@ class CoffeeBrewingRequest(CamelCaseModel): ..., title="Brew", description="Kind of drink to brew", - example="coffee", + examples=["coffee"], ) @@ -20,7 +20,7 @@ class CoffeeBrewingResponse(CamelCaseModel): ok: bool = Field( ..., title="OK", - example=True, + examples=[True], ) @@ -29,12 +29,12 @@ class TeaPotError(CamelCaseModel): ok: bool = Field( ..., title="OK", - example=False, + examples=[False], ) error_message: str = Field( ..., title="Error message", - example="I'm a teapot", + examples=["I'm a teapot"], ) diff --git a/definition_tooling/converter/tests/data/Company/BasicInfo.py b/definition_tooling/converter/tests/data/Company/BasicInfo.py index b287566..9ae1fab 100644 --- a/definition_tooling/converter/tests/data/Company/BasicInfo.py +++ b/definition_tooling/converter/tests/data/Company/BasicInfo.py @@ -12,20 +12,20 @@ class BasicCompanyInfoRequest(CamelCaseModel): ..., title="Company ID", description="The ID of the company", - example="2464491-9", + examples=["2464491-9"], ) class BasicCompanyInfoResponse(CamelCaseModel): name: str = Field( - ..., title="Name of the company", example="Digital Living International Oy" + ..., title="Name of the company", examples=["Digital Living International Oy"] ) - company_id: str = Field(..., title="ID of the company", example="2464491-9") + company_id: str = Field(..., title="ID of the company", examples=["2464491-9"]) company_form: str = Field( - ..., title="The company form of the company", example="LLC" + ..., title="The company form of the company", examples=["LLC"] ) registration_date: str = Field( - ..., title="Date of registration for the company", example="2012-02-23" + ..., title="Date of registration for the company", examples=["2012-02-23"] ) diff --git a/definition_tooling/converter/tests/data/Weather/Current/Metric.py b/definition_tooling/converter/tests/data/Weather/Current/Metric.py index 03664dc..4b78427 100644 --- a/definition_tooling/converter/tests/data/Weather/Current/Metric.py +++ b/definition_tooling/converter/tests/data/Weather/Current/Metric.py @@ -10,7 +10,7 @@ class CurrentWeatherMetricRequest(CamelCaseModel): description="The latitude coordinate of the desired location", ge=-90.0, le=90.0, - example=60.192059, + examples=[60.192059], ) lon: float = Field( ..., @@ -18,26 +18,30 @@ class CurrentWeatherMetricRequest(CamelCaseModel): description="The longitude coordinate of the desired location", ge=-180.0, le=180.0, - example=24.945831, + examples=[24.945831], ) class CurrentWeatherMetricResponse(CamelCaseModel): - humidity: float = Field(..., title="Current relative air humidity in %", example=72) - pressure: float = Field(..., title="Current air pressure in hPa", example=1007) + humidity: float = Field( + ..., title="Current relative air humidity in %", examples=[72] + ) + pressure: float = Field(..., title="Current air pressure in hPa", examples=[1007]) rain: bool = Field( ..., title="Rain status", description="If it's currently raining or not." ) temp: float = Field( - ..., title="Current temperature in Celsius", example=17.3, ge=-273.15 + ..., title="Current temperature in Celsius", examples=[17.3], ge=-273.15 + ) + wind_speed: float = Field( + ..., title="Current wind speed in m/s", examples=[2.1], ge=0 ) - wind_speed: float = Field(..., title="Current wind speed in m/s", example=2.1, ge=0) wind_direction: float = Field( ..., title="Current wind direction in meteorological wind direction degrees", ge=0, le=360, - example=220.0, + examples=[220.0], ) diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index 36002f4..9de8c1e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,25 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + [[package]] name = "anyio" -version = "3.7.0" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.7" files = [ - {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, - {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] @@ -17,7 +28,7 @@ idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] @@ -137,41 +148,6 @@ ordered-set = ">=4.0.2,<4.2.0" cli = ["click (==8.1.3)", "pyyaml (==6.0.1)"] optimize = ["orjson"] -[[package]] -name = "dnspython" -version = "2.3.0" -description = "DNS toolkit" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "dnspython-2.3.0-py3-none-any.whl", hash = "sha256:89141536394f909066cabd112e3e1a37e4e654db00a25308b0f130bc3152eb46"}, - {file = "dnspython-2.3.0.tar.gz", hash = "sha256:224e32b03eb46be70e12ef6d64e0be123a64e621ab4c0822ff6d450d52a540b9"}, -] - -[package.extras] -curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] -dnssec = ["cryptography (>=2.6,<40.0)"] -doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.11.0)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] - -[[package]] -name = "email-validator" -version = "2.0.0.post2" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.7" -files = [ - {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, - {file = "email_validator-2.0.0.post2.tar.gz", hash = "sha256:1ff6e86044200c56ae23595695c54e9614f4a9551e0e393614f764860b3d7900"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "exceptiongroup" version = "1.1.1" @@ -188,21 +164,23 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.98.0" +version = "0.103.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.7" files = [ - {file = "fastapi-0.98.0-py3-none-any.whl", hash = "sha256:f4165fb1fe3610c52cb1b8282c1480de9c34bc270f56a965aa93a884c350d605"}, - {file = "fastapi-0.98.0.tar.gz", hash = "sha256:0d3c18886f652038262b5898fec6b09f4ca92ee23e9d9b1d1d24e429f84bf27b"}, + {file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"}, + {file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" [package.extras] -all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flake8" @@ -417,56 +395,140 @@ files = [ [[package]] name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" +version = "2.4.2" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, ] [package.dependencies] -email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyflakes" @@ -676,4 +738,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4" -content-hash = "6a71d8501a9e9f1d7ae0bb1c0387703adf1d4c041797b1710496e91564775840" +content-hash = "315a0979f3fa235ee00589bd06650931b32c8c0499a13ac722132953010e6952" diff --git a/pyproject.toml b/pyproject.toml index f2ef5eb..b0b81bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,9 +18,9 @@ validate-definitions = "definition_tooling.validator.cli:cli" python = ">=3.9.0,<4" typer = {extras = ["all"], version = "^0.9.0"} deepdiff = "^6.5.0" -fastapi = "^0.98.0" +fastapi = "^0.103.2" stringcase = "^1.2.0" -pydantic = {version = "^1.10.12", extras = ["email"]} +pydantic = "^2.4.2" semver = "^3.0.1" [tool.poetry.group.dev.dependencies] From b638ef1e351c9ac9b056898f71cfbd63ebf9dbf6 Mon Sep 17 00:00:00 2001 From: ThElias Date: Fri, 13 Oct 2023 14:51:08 +0100 Subject: [PATCH 02/15] fix: path issue on windows causing C-Input/C-Output --- .../converter/tests/test_converter.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/definition_tooling/converter/tests/test_converter.py b/definition_tooling/converter/tests/test_converter.py index ecbaba5..b3d1147 100644 --- a/definition_tooling/converter/tests/test_converter.py +++ b/definition_tooling/converter/tests/test_converter.py @@ -10,10 +10,16 @@ convert_data_product_definitions, ) +# This needs to be a relative path to work correctly on Windows! +# On Windows, if the path is not relative, in other words it's absolute, we will end up +# with some C-Input/C-Output instead of the class names for the Request/Response classes +# in the final OpenAPI spec files. +definitions_dir = Path(__file__).parent.joinpath("data").relative_to(Path.cwd()) + def test_air_quality(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) + convert_data_product_definitions(definitions_dir, Path(out_dir)) dest_file = out_dir / "AirQuality" / "Current.json" assert dest_file.exists() @@ -27,7 +33,7 @@ def test_company_basic_info_errors(tmpdir, json_snapshot): Test with a definition that includes custom error message """ out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) + convert_data_product_definitions(definitions_dir, Path(out_dir)) dest_file = out_dir / "Company" / "BasicInfo.json" assert dest_file.exists() @@ -38,7 +44,7 @@ def test_company_basic_info_errors(tmpdir, json_snapshot): def test_current_weather_required_headers(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) + convert_data_product_definitions(definitions_dir, Path(out_dir)) dest_file = out_dir / "Weather" / "Current" / "Metric.json" assert dest_file.exists() @@ -49,7 +55,7 @@ def test_current_weather_required_headers(tmpdir, json_snapshot): def test_teapot_deprecated(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) + convert_data_product_definitions(definitions_dir, Path(out_dir)) dest_file = out_dir / "Appliance" / "CoffeeBrewer.json" assert dest_file.exists() @@ -79,7 +85,7 @@ def test_required_fields(): def test_summary_and_route_description(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) + convert_data_product_definitions(definitions_dir, Path(out_dir)) dest_file = out_dir / "AirQuality" / "Current.json" assert dest_file.exists() From 41b6df90ae325b2aa529426b8cff2d68af63cd8b Mon Sep 17 00:00:00 2001 From: ThElias Date: Fri, 13 Oct 2023 14:51:54 +0100 Subject: [PATCH 03/15] update snapshots to fit new openapi version --- .../test_converter/test_air_quality.json | 79 +++++++++++++++---- .../test_company_basic_info_errors.json | 77 ++++++++++++++---- ...test_current_weather_required_headers.json | 63 +++++++++++---- .../test_teapot_deprecated.json | 73 +++++++++++++---- 4 files changed, 230 insertions(+), 62 deletions(-) diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json index c7f751e..0ff482b 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json @@ -11,7 +11,9 @@ "properties": { "lat": { "description": "The latitude coordinate of the desired location", - "example": 60.192059, + "examples": [ + 60.192059 + ], "maximum": 90.0, "minimum": -90.0, "title": "Latitude", @@ -19,7 +21,9 @@ }, "lon": { "description": "The longitude coordinate of the desired location", - "example": 24.945831, + "examples": [ + 24.945831 + ], "maximum": 180.0, "minimum": -180.0, "title": "Longitude", @@ -37,15 +41,19 @@ "properties": { "airQualityIndex": { "description": "Current air quality index.\nRanges:\n0-50 Good;\n51-100 Moderate;\n101-150 Unhealthy For Sensitive Groups;\n151-200 Unhealthy;\n201-300 Very Unhealthy;\n301+ Hazardous", - "example": 30, + "examples": [ + 30 + ], "minimum": 0.0, "title": "Air Quality Index", "type": "integer" }, "attribution": { "description": "List of text to show required credits to data sources", - "example": [ - "Moscow State environmental monitoring" + "examples": [ + [ + "Moscow State environmental monitoring" + ] ], "items": { "type": "string" @@ -55,7 +63,9 @@ }, "timestamp": { "description": "Current timestamp in RFC 3339 format", - "example": "2020-04-03T13:00:00Z", + "examples": [ + "2020-04-03T13:00:00Z" + ], "title": "Timestamp", "type": "string" } @@ -145,9 +155,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "GatewayTimeout", @@ -190,9 +207,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "ServiceUnavailable", @@ -258,7 +282,7 @@ "title": "Air Quality Index", "version": "1.0.0" }, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/AirQuality/Current": { "post": { @@ -271,9 +295,16 @@ "name": "x-consent-token", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Optional consent token", - "title": "X-Consent-Token", - "type": "string" + "title": "X-Consent-Token" } }, { @@ -282,9 +313,16 @@ "name": "authorization", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization", - "type": "string" + "title": "Authorization" } }, { @@ -293,9 +331,16 @@ "name": "x-authorization-provider", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The bare domain of the system that provided the token.", - "title": "X-Authorization-Provider", - "type": "string" + "title": "X-Authorization-Provider" } } ], diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json index 87af02c..fccdc2a 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json @@ -11,7 +11,9 @@ "properties": { "companyId": { "description": "The ID of the company", - "example": "2464491-9", + "examples": [ + "2464491-9" + ], "title": "Company ID", "type": "string" } @@ -25,22 +27,30 @@ "BasicCompanyInfoResponse": { "properties": { "companyForm": { - "example": "LLC", + "examples": [ + "LLC" + ], "title": "The company form of the company", "type": "string" }, "companyId": { - "example": "2464491-9", + "examples": [ + "2464491-9" + ], "title": "ID of the company", "type": "string" }, "name": { - "example": "Digital Living International Oy", + "examples": [ + "Digital Living International Oy" + ], "title": "Name of the company", "type": "string" }, "registrationDate": { - "example": "2012-02-23", + "examples": [ + "2012-02-23" + ], "title": "Date of registration for the company", "type": "string" } @@ -131,9 +141,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "GatewayTimeout", @@ -163,9 +180,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "ServiceUnavailable", @@ -212,7 +236,7 @@ "title": "Information about a company", "version": "1.0.0" }, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/Company/BasicInfo": { "post": { @@ -225,9 +249,16 @@ "name": "x-consent-token", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Optional consent token", - "title": "X-Consent-Token", - "type": "string" + "title": "X-Consent-Token" } }, { @@ -236,9 +267,16 @@ "name": "authorization", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization", - "type": "string" + "title": "Authorization" } }, { @@ -247,9 +285,16 @@ "name": "x-authorization-provider", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The bare domain of the system that provided the token.", - "title": "X-Authorization-Provider", - "type": "string" + "title": "X-Authorization-Provider" } } ], diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json index 2d2645f..576b37f 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json @@ -11,7 +11,9 @@ "properties": { "lat": { "description": "The latitude coordinate of the desired location", - "example": 60.192059, + "examples": [ + 60.192059 + ], "maximum": 90.0, "minimum": -90.0, "title": "Latitude", @@ -19,7 +21,9 @@ }, "lon": { "description": "The longitude coordinate of the desired location", - "example": 24.945831, + "examples": [ + 24.945831 + ], "maximum": 180.0, "minimum": -180.0, "title": "Longitude", @@ -36,12 +40,16 @@ "CurrentWeatherMetricResponse": { "properties": { "humidity": { - "example": 72, + "examples": [ + 72 + ], "title": "Current relative air humidity in %", "type": "number" }, "pressure": { - "example": 1007, + "examples": [ + 1007 + ], "title": "Current air pressure in hPa", "type": "number" }, @@ -51,20 +59,26 @@ "type": "boolean" }, "temp": { - "example": 17.3, + "examples": [ + 17.3 + ], "minimum": -273.15, "title": "Current temperature in Celsius", "type": "number" }, "windDirection": { - "example": 220.0, + "examples": [ + 220.0 + ], "maximum": 360.0, "minimum": 0.0, "title": "Current wind direction in meteorological wind direction degrees", "type": "number" }, "windSpeed": { - "example": 2.1, + "examples": [ + 2.1 + ], "minimum": 0.0, "title": "Current wind speed in m/s", "type": "number" @@ -158,9 +172,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "GatewayTimeout", @@ -203,9 +224,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "ServiceUnavailable", @@ -271,7 +299,7 @@ "title": "Current weather in a given location", "version": "1.0.0" }, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/Weather/Current/Metric": { "post": { @@ -306,9 +334,16 @@ "name": "x-authorization-provider", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The bare domain of the system that provided the token.", - "title": "X-Authorization-Provider", - "type": "string" + "title": "X-Authorization-Provider" } } ], diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json index e488d0d..e51b691 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json @@ -11,7 +11,9 @@ "properties": { "brew": { "description": "Kind of drink to brew", - "example": "coffee", + "examples": [ + "coffee" + ], "title": "Brew", "type": "string" } @@ -25,7 +27,9 @@ "CoffeeBrewingResponse": { "properties": { "ok": { - "example": true, + "examples": [ + true + ], "title": "OK", "type": "boolean" } @@ -113,9 +117,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "GatewayTimeout", @@ -158,9 +169,16 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Error description", - "title": "Error message", - "type": "string" + "title": "Error message" } }, "title": "ServiceUnavailable", @@ -169,12 +187,16 @@ "TeaPotError": { "properties": { "errorMessage": { - "example": "I'm a teapot", + "examples": [ + "I'm a teapot" + ], "title": "Error message", "type": "string" }, "ok": { - "example": false, + "examples": [ + false + ], "title": "OK", "type": "boolean" } @@ -246,7 +268,7 @@ "title": "Coffee brewer", "version": "0.1.0" }, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/Appliance/CoffeeBrewer": { "post": { @@ -260,9 +282,16 @@ "name": "x-consent-token", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "Optional consent token", - "title": "X-Consent-Token", - "type": "string" + "title": "X-Consent-Token" } }, { @@ -271,9 +300,16 @@ "name": "authorization", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization", - "type": "string" + "title": "Authorization" } }, { @@ -282,9 +318,16 @@ "name": "x-authorization-provider", "required": false, "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "description": "The bare domain of the system that provided the token.", - "title": "X-Authorization-Provider", - "type": "string" + "title": "X-Authorization-Provider" } } ], From ee4808a858b4564ae39d41c4f2d69050a90fd182 Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 25 Oct 2023 08:21:09 +0100 Subject: [PATCH 04/15] feat: add github issue link --- definition_tooling/converter/tests/test_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/definition_tooling/converter/tests/test_converter.py b/definition_tooling/converter/tests/test_converter.py index b3d1147..6d101d8 100644 --- a/definition_tooling/converter/tests/test_converter.py +++ b/definition_tooling/converter/tests/test_converter.py @@ -14,6 +14,8 @@ # On Windows, if the path is not relative, in other words it's absolute, we will end up # with some C-Input/C-Output instead of the class names for the Request/Response classes # in the final OpenAPI spec files. +# Below is a link to the issue on GitHub +# https://github.com/pydantic/pydantic/issues/7860 definitions_dir = Path(__file__).parent.joinpath("data").relative_to(Path.cwd()) From b716132b0766fc21835858287cb4e7fcf9232b3b Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 25 Oct 2023 09:19:19 +0100 Subject: [PATCH 05/15] fix: return ability to validate emails --- poetry.lock | 37 ++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9de8c1e..14b4096 100644 --- a/poetry.lock +++ b/poetry.lock @@ -148,6 +148,40 @@ ordered-set = ">=4.0.2,<4.2.0" cli = ["click (==8.1.3)", "pyyaml (==6.0.1)"] optimize = ["orjson"] +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[[package]] +name = "email-validator" +version = "2.1.0.post1" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"}, + {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "exceptiongroup" version = "1.1.1" @@ -406,6 +440,7 @@ files = [ [package.dependencies] annotated-types = ">=0.4.0" +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} pydantic-core = "2.10.1" typing-extensions = ">=4.6.1" @@ -738,4 +773,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4" -content-hash = "315a0979f3fa235ee00589bd06650931b32c8c0499a13ac722132953010e6952" +content-hash = "971d77213213e32233f6f7120a9a59a77bb640ac5e1fbca988f3df80de555aa8" diff --git a/pyproject.toml b/pyproject.toml index b0b81bf..5cd1257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ typer = {extras = ["all"], version = "^0.9.0"} deepdiff = "^6.5.0" fastapi = "^0.103.2" stringcase = "^1.2.0" -pydantic = "^2.4.2" +pydantic = {version = "^2.4.2", extras = ["email"]} semver = "^3.0.1" [tool.poetry.group.dev.dependencies] From 376abe5fd454a58aba537035ca20d6f1e5f50eb1 Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 25 Oct 2023 09:19:42 +0100 Subject: [PATCH 06/15] remove unnecessary file --- log.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 log.txt diff --git a/log.txt b/log.txt deleted file mode 100644 index e69de29..0000000 From e859ee5015d8ebb749f3768c61e8a1fb8b8007ac Mon Sep 17 00:00:00 2001 From: ThElias Date: Mon, 30 Oct 2023 09:57:13 +0100 Subject: [PATCH 07/15] fix: make error message typ strictly string --- definition_tooling/api_errors.py | 38 +++++++++++-------- definition_tooling/converter/converter.py | 2 +- .../test_converter/test_air_quality.json | 32 ++++++---------- .../test_company_basic_info_errors.json | 32 ++++++---------- ...test_current_weather_required_headers.json | 32 ++++++---------- .../test_teapot_deprecated.json | 32 ++++++---------- 6 files changed, 72 insertions(+), 96 deletions(-) diff --git a/definition_tooling/api_errors.py b/definition_tooling/api_errors.py index 1f5ad6d..eed881b 100644 --- a/definition_tooling/api_errors.py +++ b/definition_tooling/api_errors.py @@ -3,29 +3,37 @@ These errors can not be overridden by the data product definition itself. """ -from typing import Optional - from pydantic import BaseModel, Field class BaseApiError(BaseModel): __status__: int + @classmethod + def get_response_spec(cls): + return {"model": cls} + -class ApiError(BaseApiError): +class ApiErrorOne(BaseApiError): type: str = Field(..., title="Error type", description="Error identifier") message: str = Field(..., title="Error message", description="Error description") -class Unauthorized(ApiError): +class ApiErrorTwo(BaseApiError): + @classmethod + def get_response_spec(cls): + return {"model": cls, "content": {"text/plain": {}, "text/html": {}}} + + +class Unauthorized(ApiErrorOne): __status__ = 401 -class Forbidden(ApiError): +class Forbidden(ApiErrorOne): __status__ = 403 -class NotFound(ApiError): +class NotFound(ApiErrorOne): __status__ = 404 @@ -45,7 +53,7 @@ class DataSourceNotFound(BaseApiError): ) -class DataSourceError(ApiError): +class DataSourceError(ApiErrorOne): __status__ = 500 @@ -57,25 +65,25 @@ class BadGateway(BaseApiError): __status__ = 502 -class ServiceUnavailable(BaseApiError): +class ServiceUnavailable(ApiErrorTwo): """ This response is reserved by Product Gateway. """ __status__ = 503 - message: Optional[str] = Field( - None, title="Error message", description="Error description" - ) + message: str = Field("", title="Error message", description="Error description") -class GatewayTimeout(BaseApiError): +class GatewayTimeout(ApiErrorTwo): """ This response is reserved by Product Gateway. """ __status__ = 504 - message: Optional[str] = Field( - None, title="Error message", description="Error description" + message: str = Field( + "", + title="Error message", + description="Error description", ) @@ -94,7 +102,7 @@ class DoesNotConformToDefinition(BaseApiError): DATA_PRODUCT_ERRORS = { - resp.__status__: {"model": resp} + resp.__status__: resp.get_response_spec() for resp in [ Unauthorized, Forbidden, diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index 71f1445..d089c67 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -102,7 +102,7 @@ class DataProductDefinition(BaseModel): deprecated: bool = False description: str error_responses: Dict[ERROR_CODE, ErrorModel] = {} - name: Optional[str] = None + name: str = "" request: Type[BaseModel] requires_authorization: bool = False requires_consent: bool = False diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json index 0ff482b..cbad19f 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json @@ -155,16 +155,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "GatewayTimeout", @@ -207,16 +201,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "ServiceUnavailable", @@ -441,7 +429,9 @@ "schema": { "$ref": "#/components/schemas/ServiceUnavailable" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Service Unavailable" }, @@ -451,7 +441,9 @@ "schema": { "$ref": "#/components/schemas/GatewayTimeout" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Gateway Timeout" }, diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json index fccdc2a..fed1b84 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json @@ -141,16 +141,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "GatewayTimeout", @@ -180,16 +174,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "ServiceUnavailable", @@ -395,7 +383,9 @@ "schema": { "$ref": "#/components/schemas/ServiceUnavailable" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Service Unavailable" }, @@ -405,7 +395,9 @@ "schema": { "$ref": "#/components/schemas/GatewayTimeout" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Gateway Timeout" }, diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json index 576b37f..070b232 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_current_weather_required_headers.json @@ -172,16 +172,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "GatewayTimeout", @@ -224,16 +218,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "ServiceUnavailable", @@ -444,7 +432,9 @@ "schema": { "$ref": "#/components/schemas/ServiceUnavailable" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Service Unavailable" }, @@ -454,7 +444,9 @@ "schema": { "$ref": "#/components/schemas/GatewayTimeout" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Gateway Timeout" }, diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json index e51b691..1f8c72b 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json @@ -117,16 +117,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "GatewayTimeout", @@ -169,16 +163,10 @@ "description": "This response is reserved by Product Gateway.", "properties": { "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Error description", - "title": "Error message" + "title": "Error message", + "type": "string" } }, "title": "ServiceUnavailable", @@ -438,7 +426,9 @@ "schema": { "$ref": "#/components/schemas/ServiceUnavailable" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Service Unavailable" }, @@ -448,7 +438,9 @@ "schema": { "$ref": "#/components/schemas/GatewayTimeout" } - } + }, + "text/html": {}, + "text/plain": {} }, "description": "Gateway Timeout" }, From 708cb66bc762a3b4220607867551eb41d8e10855 Mon Sep 17 00:00:00 2001 From: ThElias Date: Mon, 30 Oct 2023 10:52:22 +0100 Subject: [PATCH 08/15] fix: auth token default type --- definition_tooling/converter/converter.py | 8 +++---- .../test_converter/test_air_quality.json | 24 +++++-------------- .../test_company_basic_info_errors.json | 24 +++++-------------- .../test_teapot_deprecated.json | 24 +++++-------------- 4 files changed, 22 insertions(+), 58 deletions(-) diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index d089c67..3c29d43 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -140,16 +140,16 @@ def export_openapi_spec(definition: DataProductDefinition) -> dict: authorization_header_type = str authorization_header_default_value = ... else: - authorization_header_type = Optional[str] - authorization_header_default_value = None + authorization_header_type = str + authorization_header_default_value = "" if definition.requires_consent: consent_header_type = str consent_header_default_value = ... consent_header_description = "Consent token" else: - consent_header_type = Optional[str] - consent_header_default_value = None + consent_header_type = str + consent_header_default_value = "" consent_header_description = "Optional consent token" responses = { diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json index cbad19f..c5266c9 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_air_quality.json @@ -283,16 +283,10 @@ "name": "x-consent-token", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Optional consent token", - "title": "X-Consent-Token" + "title": "X-Consent-Token", + "type": "string" } }, { @@ -301,16 +295,10 @@ "name": "authorization", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization" + "title": "Authorization", + "type": "string" } }, { diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json index fed1b84..652da85 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_company_basic_info_errors.json @@ -237,16 +237,10 @@ "name": "x-consent-token", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Optional consent token", - "title": "X-Consent-Token" + "title": "X-Consent-Token", + "type": "string" } }, { @@ -255,16 +249,10 @@ "name": "authorization", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization" + "title": "Authorization", + "type": "string" } }, { diff --git a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json index 1f8c72b..bee61a7 100644 --- a/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json +++ b/definition_tooling/converter/tests/__snapshots__/test_converter/test_teapot_deprecated.json @@ -270,16 +270,10 @@ "name": "x-consent-token", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "Optional consent token", - "title": "X-Consent-Token" + "title": "X-Consent-Token", + "type": "string" } }, { @@ -288,16 +282,10 @@ "name": "authorization", "required": false, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "default": "", "description": "The login token. Value should be \"Bearer [token]\"", - "title": "Authorization" + "title": "Authorization", + "type": "string" } }, { From 914327aa08ceea83ac45014b1272e5e79ddf10d6 Mon Sep 17 00:00:00 2001 From: ThElias Date: Mon, 30 Oct 2023 14:23:02 +0100 Subject: [PATCH 09/15] refactor: improve naming --- definition_tooling/api_errors.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/definition_tooling/api_errors.py b/definition_tooling/api_errors.py index eed881b..eaff66a 100644 --- a/definition_tooling/api_errors.py +++ b/definition_tooling/api_errors.py @@ -14,26 +14,26 @@ def get_response_spec(cls): return {"model": cls} -class ApiErrorOne(BaseApiError): +class ApiError(BaseApiError): type: str = Field(..., title="Error type", description="Error identifier") message: str = Field(..., title="Error message", description="Error description") -class ApiErrorTwo(BaseApiError): +class ApiOrExternalError(BaseApiError): @classmethod def get_response_spec(cls): return {"model": cls, "content": {"text/plain": {}, "text/html": {}}} -class Unauthorized(ApiErrorOne): +class Unauthorized(ApiError): __status__ = 401 -class Forbidden(ApiErrorOne): +class Forbidden(ApiError): __status__ = 403 -class NotFound(ApiErrorOne): +class NotFound(ApiError): __status__ = 404 @@ -53,7 +53,7 @@ class DataSourceNotFound(BaseApiError): ) -class DataSourceError(ApiErrorOne): +class DataSourceError(ApiError): __status__ = 500 @@ -65,7 +65,7 @@ class BadGateway(BaseApiError): __status__ = 502 -class ServiceUnavailable(ApiErrorTwo): +class ServiceUnavailable(ApiOrExternalError): """ This response is reserved by Product Gateway. """ @@ -74,7 +74,7 @@ class ServiceUnavailable(ApiErrorTwo): message: str = Field("", title="Error message", description="Error description") -class GatewayTimeout(ApiErrorTwo): +class GatewayTimeout(ApiOrExternalError): """ This response is reserved by Product Gateway. """ From 6f9cac0357001fd55da90822ec52f85d6bb9d2ff Mon Sep 17 00:00:00 2001 From: ThElias Date: Mon, 30 Oct 2023 14:26:35 +0100 Subject: [PATCH 10/15] fix: remove field users don't fill from definition model --- definition_tooling/converter/converter.py | 11 ++++++----- definition_tooling/converter/tests/test_converter.py | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index 3c29d43..205f6a8 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -102,7 +102,6 @@ class DataProductDefinition(BaseModel): deprecated: bool = False description: str error_responses: Dict[ERROR_CODE, ErrorModel] = {} - name: str = "" request: Type[BaseModel] requires_authorization: bool = False requires_consent: bool = False @@ -123,7 +122,9 @@ def validate_error_responses(cls, v: Dict[ERROR_CODE, ErrorModel]): return v -def export_openapi_spec(definition: DataProductDefinition) -> dict: +def export_openapi_spec( + definition: DataProductDefinition, definition_name: str +) -> dict: """ Given a data product definition, create a FastAPI application and a corresponding POST route. Then export its OpenAPI spec @@ -162,7 +163,7 @@ def export_openapi_spec(definition: DataProductDefinition) -> dict: responses.update(DATA_PRODUCT_ERRORS) @app.post( - f"/{definition.name}", + f"/{definition_name}", summary=definition.title, description=definition.description, response_model=definition.response, @@ -228,9 +229,9 @@ def convert_data_product_definitions(src: Path, dest: Path) -> bool: continue # Get definition name based on file path - definition.name = p.relative_to(src).with_suffix("").as_posix() + definition_name = p.relative_to(src).with_suffix("").as_posix() - openapi = export_openapi_spec(definition) + openapi = export_openapi_spec(definition, definition_name) out_file = (dest / p.relative_to(src)).with_suffix(".json") diff --git a/definition_tooling/converter/tests/test_converter.py b/definition_tooling/converter/tests/test_converter.py index 6d101d8..3f32777 100644 --- a/definition_tooling/converter/tests/test_converter.py +++ b/definition_tooling/converter/tests/test_converter.py @@ -71,7 +71,6 @@ def test_required_fields(): DataProductDefinition( title=None, description="Description", - name="Foo/Bar", request=CamelCaseModel, response=CamelCaseModel, ) @@ -79,7 +78,6 @@ def test_required_fields(): DataProductDefinition( title="Title", description=None, - name="Foo/Bar", request=CamelCaseModel, response=CamelCaseModel, ) From 521ad09544baed72f14c0837c6c36a327abf3c73 Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 1 Nov 2023 12:06:12 +0100 Subject: [PATCH 11/15] fix: load modules with sane name to rid of workaround --- definition_tooling/converter/converter.py | 14 +++++++++----- .../converter/tests/test_converter.py | 18 +++++------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index 205f6a8..571a608 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -211,11 +211,18 @@ def convert_data_product_definitions(src: Path, dest: Path) -> bool: should_fail_hook = False modified_files = [] for p in src.glob("**/*.py"): - spec = importlib.util.spec_from_file_location(name=str(p), location=str(p)) + # Get definition name based on file path + definition_name = p.relative_to(src).with_suffix("").as_posix() + + # generate a python module name based on the definition name/path + module_name = definition_name.replace(".", "_").replace("/", ".") + print(p, module_name) + spec = importlib.util.spec_from_file_location(name=module_name, location=str(p)) if not spec.loader: raise RuntimeError(f"Failed to import {p} module") try: - module = spec.loader.load_module(str(p)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) except ValidationError as e: should_fail_hook = True print(styled_error("Validation error", p)) @@ -228,9 +235,6 @@ def convert_data_product_definitions(src: Path, dest: Path) -> bool: print(styled_error("Error finding DEFINITION variable", p)) continue - # Get definition name based on file path - definition_name = p.relative_to(src).with_suffix("").as_posix() - openapi = export_openapi_spec(definition, definition_name) out_file = (dest / p.relative_to(src)).with_suffix(".json") diff --git a/definition_tooling/converter/tests/test_converter.py b/definition_tooling/converter/tests/test_converter.py index 3f32777..e34598d 100644 --- a/definition_tooling/converter/tests/test_converter.py +++ b/definition_tooling/converter/tests/test_converter.py @@ -10,18 +10,10 @@ convert_data_product_definitions, ) -# This needs to be a relative path to work correctly on Windows! -# On Windows, if the path is not relative, in other words it's absolute, we will end up -# with some C-Input/C-Output instead of the class names for the Request/Response classes -# in the final OpenAPI spec files. -# Below is a link to the issue on GitHub -# https://github.com/pydantic/pydantic/issues/7860 -definitions_dir = Path(__file__).parent.joinpath("data").relative_to(Path.cwd()) - def test_air_quality(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(definitions_dir, Path(out_dir)) + convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) dest_file = out_dir / "AirQuality" / "Current.json" assert dest_file.exists() @@ -35,7 +27,7 @@ def test_company_basic_info_errors(tmpdir, json_snapshot): Test with a definition that includes custom error message """ out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(definitions_dir, Path(out_dir)) + convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) dest_file = out_dir / "Company" / "BasicInfo.json" assert dest_file.exists() @@ -46,7 +38,7 @@ def test_company_basic_info_errors(tmpdir, json_snapshot): def test_current_weather_required_headers(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(definitions_dir, Path(out_dir)) + convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) dest_file = out_dir / "Weather" / "Current" / "Metric.json" assert dest_file.exists() @@ -57,7 +49,7 @@ def test_current_weather_required_headers(tmpdir, json_snapshot): def test_teapot_deprecated(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(definitions_dir, Path(out_dir)) + convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) dest_file = out_dir / "Appliance" / "CoffeeBrewer.json" assert dest_file.exists() @@ -85,7 +77,7 @@ def test_required_fields(): def test_summary_and_route_description(tmpdir, json_snapshot): out_dir = tmpdir.mkdir("output") - convert_data_product_definitions(definitions_dir, Path(out_dir)) + convert_data_product_definitions(Path(__file__).parent / "data", Path(out_dir)) dest_file = out_dir / "AirQuality" / "Current.json" assert dest_file.exists() From 7b8237e3e302b32fb1ae24a92cf7c65e255a4f65 Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 1 Nov 2023 12:37:12 +0100 Subject: [PATCH 12/15] fix: remove logging --- definition_tooling/converter/converter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index 571a608..036bace 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -216,7 +216,6 @@ def convert_data_product_definitions(src: Path, dest: Path) -> bool: # generate a python module name based on the definition name/path module_name = definition_name.replace(".", "_").replace("/", ".") - print(p, module_name) spec = importlib.util.spec_from_file_location(name=module_name, location=str(p)) if not spec.loader: raise RuntimeError(f"Failed to import {p} module") From 84aba33a87750e29f8204397df1ce0b23314adc1 Mon Sep 17 00:00:00 2001 From: ThElias Date: Tue, 7 Nov 2023 08:31:19 +0100 Subject: [PATCH 13/15] chore: update fastapi --- poetry.lock | 22 +++++++++++----------- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 14b4096..d2fd4ca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -198,20 +198,20 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.103.2" +version = "0.104.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"}, - {file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"}, + {file = "fastapi-0.104.1-py3-none-any.whl", hash = "sha256:752dc31160cdbd0436bb93bad51560b57e525cbb1d4bbf6f4904ceee75548241"}, + {file = "fastapi-0.104.1.tar.gz", hash = "sha256:e5e4540a7c5e1dcfbbcf5b903c234feddcdcd881f191977a1c5dfd917487e7ae"}, ] [package.dependencies] anyio = ">=3.7.1,<4.0.0" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.27.0,<0.28.0" -typing-extensions = ">=4.5.0" +typing-extensions = ">=4.8.0" [package.extras] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] @@ -761,16 +761,16 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "typing-extensions" -version = "4.6.2" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"}, - {file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4" -content-hash = "971d77213213e32233f6f7120a9a59a77bb640ac5e1fbca988f3df80de555aa8" +content-hash = "19faec72814dd9ec20e5aec87f92cb85b57da689882a4476046c7db8141d03d0" diff --git a/pyproject.toml b/pyproject.toml index 5cd1257..4644a2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ validate-definitions = "definition_tooling.validator.cli:cli" python = ">=3.9.0,<4" typer = {extras = ["all"], version = "^0.9.0"} deepdiff = "^6.5.0" -fastapi = "^0.103.2" +fastapi = "^0.104.1" stringcase = "^1.2.0" pydantic = {version = "^2.4.2", extras = ["email"]} semver = "^3.0.1" From 7fad6d6de9a76b1ad84a8735d08833fc27aea546 Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 8 Nov 2023 14:47:34 +0100 Subject: [PATCH 14/15] refactor: use semver __get_pydantic_core_schema__ --- definition_tooling/converter/converter.py | 48 ++++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/definition_tooling/converter/converter.py b/definition_tooling/converter/converter.py index 036bace..b073935 100644 --- a/definition_tooling/converter/converter.py +++ b/definition_tooling/converter/converter.py @@ -2,11 +2,18 @@ import json import subprocess from pathlib import Path -from typing import Dict, List, Optional, Type +from typing import Any, Callable, Dict, List, Optional, Type from deepdiff import DeepDiff from fastapi import FastAPI, Header -from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + GetJsonSchemaHandler, + ValidationError, + field_validator, +) from pydantic.json_schema import JsonSchemaValue from pydantic_core import core_schema from rich import print @@ -71,28 +78,39 @@ class PydanticVersion(Version): """ This class is based on: https://python-semver.readthedocs.io/en/latest/advanced/combine-pydantic-and-semver.html - - Note: This won't work with Pydantic 2, for more details see: - https://docs.pydantic.dev/2.3/migration/ """ - @classmethod - def _parse(cls, version): - return cls.parse(version) - @classmethod def __get_pydantic_core_schema__( - cls, source_type, _handler + cls, + _source_type: Any, + _handler: Callable[[Any], core_schema.CoreSchema], ) -> core_schema.CoreSchema: - """Return a list of validator methods for pydantic models.""" - return core_schema.no_info_wrap_validator_function( - cls.parse, - core_schema.str_schema(), + def validate_from_str(value: str) -> Version: + return Version.parse(value) + + from_str_schema = core_schema.chain_schema( + [ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(validate_from_str), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_str_schema, + python_schema=core_schema.union_schema( + [ + core_schema.is_instance_schema(Version), + from_str_schema, + ] + ), serialization=core_schema.to_string_ser_schema(), ) @classmethod - def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue: + def __get_pydantic_json_schema__( + cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: """Inject/mutate the pydantic field schema in-place.""" return handler(core_schema.str_schema()) From 7671345332b1e8ccef8a5aa409928290e690b80b Mon Sep 17 00:00:00 2001 From: ThElias Date: Wed, 8 Nov 2023 14:56:04 +0100 Subject: [PATCH 15/15] chorwe: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4644a2b..1c182ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ioxio-data-product-definition-tooling" -version = "0.3.0" +version = "0.4.0" description = "IOXIO Data Product Definition Tooling" authors = ["IOXIO Ltd"] license = "BSD-3-Clause"