From b5ccd97ec722504c6be7c9e0addd27e9c031baea Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 27 Sep 2023 19:46:18 +0200 Subject: [PATCH 1/4] Use v1 API in pydantic v2 --- optimade/adapters/base.py | 2 +- optimade/models/baseinfo.py | 2 +- optimade/models/entries.py | 2 +- optimade/models/index_metadb.py | 2 +- optimade/models/jsonapi.py | 2 +- optimade/models/links.py | 2 +- optimade/models/optimade_json.py | 2 +- optimade/models/references.py | 7 ++++++- optimade/models/responses.py | 2 +- optimade/models/structures.py | 2 +- optimade/models/utils.py | 2 +- optimade/server/config.py | 4 ++-- optimade/server/exception_handlers.py | 2 +- optimade/server/query_params.py | 2 +- optimade/utils.py | 2 +- optimade/validator/config.py | 2 +- optimade/validator/utils.py | 4 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- tests/models/test_entries.py | 2 +- tests/models/test_jsonapi.py | 2 +- tests/models/test_links.py | 2 +- tests/models/test_optimade_json.py | 2 +- tests/models/test_references.py | 4 ++-- tests/models/test_structures.py | 2 +- tests/models/test_utils.py | 2 +- 26 files changed, 34 insertions(+), 29 deletions(-) diff --git a/optimade/adapters/base.py b/optimade/adapters/base.py index f1d4bcb63..da22e516a 100644 --- a/optimade/adapters/base.py +++ b/optimade/adapters/base.py @@ -21,7 +21,7 @@ import re from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union -from pydantic import BaseModel # pylint: disable=no-name-in-module +from pydantic.v1 import BaseModel # pylint: disable=no-name-in-module from optimade.adapters.logger import LOGGER from optimade.models.entries import EntryResource diff --git a/optimade/models/baseinfo.py b/optimade/models/baseinfo.py index 3374b6a02..abafb4099 100644 --- a/optimade/models/baseinfo.py +++ b/optimade/models/baseinfo.py @@ -2,7 +2,7 @@ import re from typing import Dict, List, Optional -from pydantic import AnyHttpUrl, BaseModel, Field, root_validator, validator +from pydantic.v1 import AnyHttpUrl, BaseModel, Field, root_validator, validator from optimade.models.jsonapi import Resource from optimade.models.utils import SemanticVersion, StrictField diff --git a/optimade/models/entries.py b/optimade/models/entries.py index 7850565ea..6b91b1407 100644 --- a/optimade/models/entries.py +++ b/optimade/models/entries.py @@ -2,7 +2,7 @@ from datetime import datetime from typing import Dict, List, Optional -from pydantic import BaseModel, validator # pylint: disable=no-name-in-module +from pydantic.v1 import BaseModel, validator # pylint: disable=no-name-in-module from optimade.models.jsonapi import Attributes, Relationships, Resource from optimade.models.optimade_json import DataType, Relationship diff --git a/optimade/models/index_metadb.py b/optimade/models/index_metadb.py index 7c48d666c..6d6c07ff9 100644 --- a/optimade/models/index_metadb.py +++ b/optimade/models/index_metadb.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Dict, Union -from pydantic import BaseModel, Field # pylint: disable=no-name-in-module +from pydantic.v1 import BaseModel, Field # pylint: disable=no-name-in-module from optimade.models.baseinfo import BaseInfoAttributes, BaseInfoResource from optimade.models.jsonapi import BaseResource diff --git a/optimade/models/jsonapi.py b/optimade/models/jsonapi.py index 23e0db241..a4e9d13f9 100644 --- a/optimade/models/jsonapi.py +++ b/optimade/models/jsonapi.py @@ -3,7 +3,7 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Type, Union -from pydantic import ( # pylint: disable=no-name-in-module +from pydantic.v1 import ( # pylint: disable=no-name-in-module AnyUrl, BaseModel, parse_obj_as, diff --git a/optimade/models/links.py b/optimade/models/links.py index 9cb316112..924a93883 100644 --- a/optimade/models/links.py +++ b/optimade/models/links.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional, Union -from pydantic import AnyUrl, root_validator # pylint: disable=no-name-in-module +from pydantic.v1 import AnyUrl, root_validator # pylint: disable=no-name-in-module from optimade.models.entries import EntryResource from optimade.models.jsonapi import Attributes, Link diff --git a/optimade/models/optimade_json.py b/optimade/models/optimade_json.py index bad738057..efcb35c74 100644 --- a/optimade/models/optimade_json.py +++ b/optimade/models/optimade_json.py @@ -4,7 +4,7 @@ from enum import Enum from typing import Any, Dict, List, Optional, Type, Union -from pydantic import AnyHttpUrl, AnyUrl, BaseModel, EmailStr, root_validator +from pydantic.v1 import AnyHttpUrl, AnyUrl, BaseModel, EmailStr, root_validator from optimade.models import jsonapi from optimade.models.utils import SemanticVersion, StrictField diff --git a/optimade/models/references.py b/optimade/models/references.py index afdd2f48f..233d6be3e 100644 --- a/optimade/models/references.py +++ b/optimade/models/references.py @@ -1,8 +1,13 @@ # pylint: disable=line-too-long,no-self-argument from typing import List, Optional -from pydantic import AnyUrl, BaseModel, validator # pylint: disable=no-name-in-module +from pydantic.v1 import ( + AnyUrl, + BaseModel, + validator, +) +# pylint: disable=no-name-in-module from optimade.models.entries import EntryResource, EntryResourceAttributes from optimade.models.utils import OptimadeField, SupportLevel diff --git a/optimade/models/responses.py b/optimade/models/responses.py index 01845dc82..7060e9954 100644 --- a/optimade/models/responses.py +++ b/optimade/models/responses.py @@ -1,7 +1,7 @@ # pylint: disable=no-self-argument from typing import Any, Dict, List, Optional, Union -from pydantic import Field, root_validator +from pydantic.v1 import Field, root_validator from optimade.models.baseinfo import BaseInfoResource from optimade.models.entries import EntryInfoResource, EntryResource diff --git a/optimade/models/structures.py b/optimade/models/structures.py index aa89afe69..e7e8f7a48 100644 --- a/optimade/models/structures.py +++ b/optimade/models/structures.py @@ -4,7 +4,7 @@ from enum import Enum, IntEnum from typing import List, Optional, Union -from pydantic import BaseModel, conlist, root_validator, validator +from pydantic.v1 import BaseModel, conlist, root_validator, validator from optimade.models.entries import EntryResource, EntryResourceAttributes from optimade.models.utils import ( diff --git a/optimade/models/utils.py b/optimade/models/utils.py index 7e04bac07..80515e6ba 100644 --- a/optimade/models/utils.py +++ b/optimade/models/utils.py @@ -7,8 +7,8 @@ from functools import reduce from typing import TYPE_CHECKING, List, Optional, Union -from pydantic import Field from pydantic.fields import FieldInfo +from pydantic.v1 import Field if TYPE_CHECKING: # pragma: no cover from typing import Any diff --git a/optimade/server/config.py b/optimade/server/config.py index e84fc451e..2bd7826e5 100644 --- a/optimade/server/config.py +++ b/optimade/server/config.py @@ -4,14 +4,14 @@ from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Tuple, Union -from pydantic import ( # pylint: disable=no-name-in-module +from pydantic.env_settings import SettingsSourceCallable +from pydantic.v1 import ( # pylint: disable=no-name-in-module AnyHttpUrl, BaseSettings, Field, root_validator, validator, ) -from pydantic.env_settings import SettingsSourceCallable from optimade import __api_version__, __version__ from optimade.models import Implementation, Provider diff --git a/optimade/server/exception_handlers.py b/optimade/server/exception_handlers.py index 06fc083a8..5807ab0c2 100644 --- a/optimade/server/exception_handlers.py +++ b/optimade/server/exception_handlers.py @@ -5,7 +5,7 @@ from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError, StarletteHTTPException from lark.exceptions import VisitError -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.exceptions import BadRequest, OptimadeHTTPException from optimade.models import ErrorResponse, ErrorSource, OptimadeError diff --git a/optimade/server/query_params.py b/optimade/server/query_params.py index a955d8ef7..b4dbc1d5b 100644 --- a/optimade/server/query_params.py +++ b/optimade/server/query_params.py @@ -3,7 +3,7 @@ from warnings import warn from fastapi import Query -from pydantic import EmailStr # pylint: disable=no-name-in-module +from pydantic.v1 import EmailStr # pylint: disable=no-name-in-module from optimade.exceptions import BadRequest from optimade.server.config import CONFIG diff --git a/optimade/utils.py b/optimade/utils.py index cb3039565..dcbe70ee3 100644 --- a/optimade/utils.py +++ b/optimade/utils.py @@ -6,7 +6,7 @@ import json from typing import Container, Iterable, List, Optional -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.models.links import LinksResource diff --git a/optimade/validator/config.py b/optimade/validator/config.py index 390a3f7b8..60b0e6942 100644 --- a/optimade/validator/config.py +++ b/optimade/validator/config.py @@ -9,7 +9,7 @@ from typing import Any, Container, Dict, List, Set -from pydantic import BaseSettings, Field +from pydantic.v1 import BaseSettings, Field from optimade.models import ( DataType, diff --git a/optimade/validator/utils.py b/optimade/validator/utils.py index 81e5e11b7..d71541b11 100644 --- a/optimade/validator/utils.py +++ b/optimade/validator/utils.py @@ -21,7 +21,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple import requests -from pydantic import Field, ValidationError +from pydantic.v1 import Field, ValidationError from optimade import __version__ from optimade.models import ( @@ -366,7 +366,7 @@ def wrapper( else: message = msg.split("\n") if validator.verbosity > 1: - # ValidationErrors from pydantic already include very detailed errors + # ValidationErrors from pydantic.v1 already include very detailed errors # that get duplicated in the traceback if not isinstance(result, ValidationError): message += traceback.split("\n") diff --git a/pyproject.toml b/pyproject.toml index 8fe13c214..f8f6c14ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ requires-python = ">=3.9" dependencies = [ "lark~=1.1", - "pydantic~=1.10,>=1.10.2,!=1.10.7", + "pydantic~=2.3", "email_validator>=1.2", "requests~=2.28", ] diff --git a/requirements.txt b/requirements.txt index d751af95f..9e463418a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ email_validator==2.0.0.post2 lark==1.1.7 -pydantic==1.10.9 +pydantic==2.3.0 pyyaml==6.0 requests==2.31.0 uvicorn==0.23.2 diff --git a/tests/models/test_entries.py b/tests/models/test_entries.py index a3ab7e318..72daea578 100644 --- a/tests/models/test_entries.py +++ b/tests/models/test_entries.py @@ -1,5 +1,5 @@ import pytest -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.models.entries import EntryRelationships diff --git a/tests/models/test_jsonapi.py b/tests/models/test_jsonapi.py index b2e09cb4a..5464e0856 100644 --- a/tests/models/test_jsonapi.py +++ b/tests/models/test_jsonapi.py @@ -1,5 +1,5 @@ import pytest -from pydantic import ValidationError +from pydantic.v1 import ValidationError def test_hashability(): diff --git a/tests/models/test_links.py b/tests/models/test_links.py index c65cc9edf..f8b159f4d 100644 --- a/tests/models/test_links.py +++ b/tests/models/test_links.py @@ -14,7 +14,7 @@ def test_good_links(starting_links, mapper): def test_bad_links(starting_links, mapper): """Check badly formed links""" - from pydantic import ValidationError + from pydantic.v1 import ValidationError bad_links = [ {"aggregate": "wrong"}, diff --git a/tests/models/test_optimade_json.py b/tests/models/test_optimade_json.py index 49c91d985..885a18065 100644 --- a/tests/models/test_optimade_json.py +++ b/tests/models/test_optimade_json.py @@ -1,5 +1,5 @@ import pytest -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.models import DataType, Provider diff --git a/tests/models/test_references.py b/tests/models/test_references.py index de86bebb4..87880888d 100644 --- a/tests/models/test_references.py +++ b/tests/models/test_references.py @@ -1,6 +1,6 @@ # pylint: disable=no-member import pytest -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.models.references import ReferenceResource @@ -22,7 +22,7 @@ def test_more_good_references(good_references, mapper): def test_bad_references(mapper): """Check badly formed references""" - from pydantic import ValidationError + from pydantic.v1 import ValidationError bad_refs = [ {"id": "AAAA", "type": "references", "doi": "10.1234/1234"}, # bad id diff --git a/tests/models/test_structures.py b/tests/models/test_structures.py index 213dfd5eb..e38feed12 100644 --- a/tests/models/test_structures.py +++ b/tests/models/test_structures.py @@ -2,7 +2,7 @@ import itertools import pytest -from pydantic import ValidationError +from pydantic.v1 import ValidationError from optimade.models.structures import CORRELATED_STRUCTURE_FIELDS, StructureResource from optimade.warnings import MissingExpectedField diff --git a/tests/models/test_utils.py b/tests/models/test_utils.py index e5b3f7502..2cdc541d5 100644 --- a/tests/models/test_utils.py +++ b/tests/models/test_utils.py @@ -1,7 +1,7 @@ from typing import Callable, List import pytest -from pydantic import BaseModel, Field, ValidationError +from pydantic.v1 import BaseModel, Field, ValidationError from optimade.models.utils import OptimadeField, StrictField, SupportLevel From 452f3568634ac84f1efcaa7817a9805ede7a8cc0 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Fri, 11 Aug 2023 12:09:42 +0100 Subject: [PATCH 2/4] Use pydantic-settings --- optimade/server/config.py | 17 ++++++++--------- pyproject.toml | 4 ++-- requirements.txt | 3 ++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/optimade/server/config.py b/optimade/server/config.py index 2bd7826e5..aa1169795 100644 --- a/optimade/server/config.py +++ b/optimade/server/config.py @@ -4,14 +4,13 @@ from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Tuple, Union -from pydantic.env_settings import SettingsSourceCallable from pydantic.v1 import ( # pylint: disable=no-name-in-module AnyHttpUrl, - BaseSettings, Field, root_validator, validator, ) +from pydantic_settings import BaseSettings, PydanticBaseSettingsSource from optimade import __api_version__, __version__ from optimade.models import Implementation, Provider @@ -71,14 +70,14 @@ def config_file_settings(settings: BaseSettings) -> Dict[str, Any]: """Configuration file settings source. Based on the example in the - [pydantic documentation](https://pydantic-docs.helpmanual.io/usage/settings/#adding-sources), + [pydantic documentation](https://docs.pydantic.dev/latest/usage/pydantic_settings/#customise-settings-sources), this function loads ServerConfig settings from a configuration file. The file must be of either type JSON or YML/YAML. Parameters: - settings: The `pydantic.BaseSettings` class using this function as a - `pydantic.SettingsSourceCallable`. + settings: The `pydantic_settings.BaseSettings` class using this function as a + `pydantic_settings.PydanticBaseSettingsSource`. Returns: Dictionary of settings as read from a file. @@ -345,10 +344,10 @@ class Config: @classmethod def customise_sources( cls, - init_settings: SettingsSourceCallable, - env_settings: SettingsSourceCallable, - file_secret_settings: SettingsSourceCallable, - ) -> Tuple[SettingsSourceCallable, ...]: + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: """ **Priority of config settings sources**: diff --git a/pyproject.toml b/pyproject.toml index f8f6c14ea..363ece527 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,8 @@ classifiers = [ requires-python = ">=3.9" dependencies = [ "lark~=1.1", - "pydantic~=2.3", + "pydantic~=2.0", + "pydantic-settings~=2.0", "email_validator>=1.2", "requests~=2.28", ] @@ -65,7 +66,6 @@ elastic = ["elasticsearch-dsl~=7.4,<8.0", "elasticsearch~=7.17"] mongo = ["pymongo~=4.0", "mongomock~=4.1"] server = [ "uvicorn[standard]~=0.19", - "fastapi>=0.103.1", "pyyaml~=6.0", "optimade[mongo]", ] diff --git a/requirements.txt b/requirements.txt index 9e463418a..2894031ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ email_validator==2.0.0.post2 lark==1.1.7 -pydantic==2.3.0 +pydantic==2.4.1 +pydantic_settings==2.0.3 pyyaml==6.0 requests==2.31.0 uvicorn==0.23.2 From 2e99fcf02a853e8c1bd42529be375475256baa31 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 27 Sep 2023 20:24:00 +0200 Subject: [PATCH 3/4] Allow class-based config pydantic warning through tests --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 363ece527..a89df33d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,6 +150,7 @@ filterwarnings = [ "ignore:.*has an unrecognised prefix.*:", "ignore:.*pkg_resources is deprecated as an API*:DeprecationWarning", "ignore:.*Deprecated call to `pkg_resources.declare_namespace*:DeprecationWarning", + "ignore:.*Support for class-based `config` is deprecated, use ConfigDict instead*:DeprecationWarning", ] testpaths = "tests" addopts = "-rs" From 510ad39cc4842ec466e3ce77a268c50d60f24b35 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 27 Sep 2023 20:27:29 +0200 Subject: [PATCH 4/4] Try to naively use fieldinfo hack with pydantic v2 --- optimade/models/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimade/models/utils.py b/optimade/models/utils.py index 80515e6ba..4b9f95dc9 100644 --- a/optimade/models/utils.py +++ b/optimade/models/utils.py @@ -7,8 +7,8 @@ from functools import reduce from typing import TYPE_CHECKING, List, Optional, Union -from pydantic.fields import FieldInfo from pydantic.v1 import Field +from pydantic.v1.fields import FieldInfo if TYPE_CHECKING: # pragma: no cover from typing import Any