From 3c1b731c2f6ae57ba5fb3a314bd39caca9ac95c3 Mon Sep 17 00:00:00 2001 From: Wilfried BARADAT Date: Fri, 12 Apr 2024 17:02:13 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=BD=EF=B8=8F(project)=20replace=20depr?= =?UTF-8?q?ecated=20`parse=5Fobj=5Fas`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace soon-to-be-deprecated `parse_obj_as` with `TypeAdapter` after Pydantic V2 migration. --- CHANGELOG.md | 1 + src/ralph/backends/data/async_lrs.py | 4 ++-- src/ralph/backends/data/lrs.py | 10 ++++++++-- src/ralph/conf.py | 6 ++++-- tests/api/auth/test_oidc.py | 6 ++++-- tests/backends/data/test_async_lrs.py | 6 ++++-- tests/backends/data/test_async_ws.py | 8 ++++---- tests/fixtures/backends.py | 4 ++-- tests/test_conf.py | 11 +++++------ 9 files changed, 34 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f475c249..9d7da21dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to - Upgrade `pydantic` to `2.7.0` - Migrate model tests from hypothesis strategies to polyfactory +- Replace soon-to-be deprecated `parse_obj_as` with `TypeAdapter` ## [4.2.0] - 2024-04-08 diff --git a/src/ralph/backends/data/async_lrs.py b/src/ralph/backends/data/async_lrs.py index fdddbb29c..1e92eb76d 100644 --- a/src/ralph/backends/data/async_lrs.py +++ b/src/ralph/backends/data/async_lrs.py @@ -6,7 +6,7 @@ from urllib.parse import ParseResult, parse_qs, urljoin, urlparse from httpx import AsyncClient, HTTPError, HTTPStatusError, RequestError -from pydantic import AnyHttpUrl, PositiveInt, parse_obj_as +from pydantic import AnyHttpUrl, PositiveInt, TypeAdapter from ralph.backends.data.base import ( AsyncWritable, @@ -44,7 +44,7 @@ def __init__(self, settings: Optional[LRSDataBackendSettings] = None) -> None: If `settings` is `None`, a default settings instance is used instead. """ super().__init__(settings) - self.base_url = parse_obj_as(AnyHttpUrl, self.settings.BASE_URL) + self.base_url = TypeAdapter(AnyHttpUrl).validate_python(self.settings.BASE_URL) self.auth = (self.settings.USERNAME, self.settings.PASSWORD) self._client = None diff --git a/src/ralph/backends/data/lrs.py b/src/ralph/backends/data/lrs.py index 0225de9ea..033e26402 100644 --- a/src/ralph/backends/data/lrs.py +++ b/src/ralph/backends/data/lrs.py @@ -6,7 +6,13 @@ from urllib.parse import ParseResult, parse_qs, urljoin, urlparse from httpx import Client, HTTPError, HTTPStatusError, RequestError -from pydantic import AnyHttpUrl, BaseModel, Field, PositiveInt, parse_obj_as +from pydantic import ( + AnyHttpUrl, + BaseModel, + Field, + PositiveInt, + TypeAdapter, +) from pydantic_settings import SettingsConfigDict from typing_extensions import Annotated @@ -90,7 +96,7 @@ def __init__(self, settings: Optional[LRSDataBackendSettings] = None) -> None: If `settings` is `None`, a default settings instance is used instead. """ super().__init__(settings) - self.base_url = parse_obj_as(AnyHttpUrl, self.settings.BASE_URL) + self.base_url = TypeAdapter(AnyHttpUrl).validate_python(self.settings.BASE_URL) self.auth = (self.settings.USERNAME, self.settings.PASSWORD) self._client = None diff --git a/src/ralph/conf.py b/src/ralph/conf.py index 8b35f66a0..6dfba7ed6 100644 --- a/src/ralph/conf.py +++ b/src/ralph/conf.py @@ -13,8 +13,8 @@ ConfigDict, Field, StringConstraints, + TypeAdapter, model_validator, - parse_obj_as, ) from pydantic_settings import BaseSettings, SettingsConfigDict from typing_extensions import Annotated @@ -198,7 +198,9 @@ class Settings(BaseSettings): }, } PARSERS: ParserSettings = ParserSettings() - RUNSERVER_AUTH_BACKENDS: AuthBackends = parse_obj_as(AuthBackends, "Basic") + RUNSERVER_AUTH_BACKENDS: AuthBackends = TypeAdapter(AuthBackends).validate_python( + "Basic" + ) RUNSERVER_AUTH_OIDC_AUDIENCE: Optional[str] = None RUNSERVER_AUTH_OIDC_ISSUER_URI: Optional[AnyHttpUrl] = None RUNSERVER_BACKEND: str = "es" diff --git a/tests/api/auth/test_oidc.py b/tests/api/auth/test_oidc.py index bb385eb2e..e5d9f34e7 100644 --- a/tests/api/auth/test_oidc.py +++ b/tests/api/auth/test_oidc.py @@ -2,7 +2,7 @@ import pytest import responses -from pydantic import parse_obj_as +from pydantic import TypeAdapter from ralph.api.auth.oidc import discover_provider, get_public_keys from ralph.conf import AuthBackend @@ -40,7 +40,9 @@ async def test_api_auth_oidc_get_whoami_valid( assert response.status_code == 200 assert len(response.json().keys()) == 2 assert response.json()["agent"] == {"openid": "https://iss.example.com/123|oidc"} - assert parse_obj_as(BaseXapiAgentWithOpenId, response.json()["agent"]) + assert TypeAdapter(BaseXapiAgentWithOpenId).validate_python( + response.json()["agent"] + ) assert sorted(response.json()["scopes"]) == ["all", "profile/read"] assert "target" not in response.json() diff --git a/tests/backends/data/test_async_lrs.py b/tests/backends/data/test_async_lrs.py index ea81a9b78..778422225 100644 --- a/tests/backends/data/test_async_lrs.py +++ b/tests/backends/data/test_async_lrs.py @@ -11,7 +11,7 @@ import httpx import pytest from httpx import HTTPStatusError, RequestError -from pydantic import AnyHttpUrl, AnyUrl, parse_obj_as +from pydantic import AnyHttpUrl, AnyUrl, TypeAdapter from pytest_httpx import HTTPXMock from ralph.backends.data.async_lrs import AsyncLRSDataBackend @@ -46,7 +46,9 @@ def test_backends_data_async_lrs_default_instantiation(monkeypatch, fs, lrs_back assert backend_class.settings_class == LRSDataBackendSettings backend = backend_class() assert backend.query_class == LRSStatementsQuery - assert backend.base_url == parse_obj_as(AnyHttpUrl, "http://0.0.0.0:8100") + assert backend.base_url == TypeAdapter(AnyHttpUrl).validate_python( + "http://0.0.0.0:8100" + ) assert backend.auth == ("ralph", "secret") assert backend.settings.HEADERS == LRSHeaders() assert backend.settings.LOCALE_ENCODING == "utf8" diff --git a/tests/backends/data/test_async_ws.py b/tests/backends/data/test_async_ws.py index 739ff25fc..9cde16fd3 100644 --- a/tests/backends/data/test_async_ws.py +++ b/tests/backends/data/test_async_ws.py @@ -6,7 +6,7 @@ import pytest import websockets -from pydantic import AnyUrl, parse_obj_as +from pydantic import AnyUrl, TypeAdapter from ralph.backends.data.async_ws import AsyncWSDataBackend, WSDataBackendSettings from ralph.backends.data.base import DataBackendStatus @@ -49,7 +49,7 @@ def test_backends_data_async_ws_instantiation_with_settings(monkeypatch): uri = f"ws://{WS_TEST_HOST}:{WS_TEST_PORT}" settings = WSDataBackendSettings(URI=uri) backend = AsyncWSDataBackend(settings) - assert backend.settings.URI == parse_obj_as(AnyUrl, uri) + assert backend.settings.URI == TypeAdapter(AnyUrl).validate_python(uri) assert backend.settings.LOCALE_ENCODING == "utf8" assert backend.settings.READ_CHUNK_SIZE == 500 assert backend.settings.WRITE_CHUNK_SIZE == 500 @@ -59,7 +59,7 @@ def test_backends_data_async_ws_instantiation_with_settings(monkeypatch): monkeypatch.setenv("RALPH_BACKENDS__DATA__WS__URI", "ws://foo") backend = AsyncWSDataBackend() assert backend.settings.READ_CHUNK_SIZE == 1 - assert backend.settings.URI == parse_obj_as(AnyUrl, "ws://foo") + assert backend.settings.URI == TypeAdapter(AnyUrl).validate_python("ws://foo") @pytest.mark.anyio @@ -79,7 +79,7 @@ async def test_backends_data_async_ws_status_with_error_status(ws, events, caplo "[Errno 111] Connect call failed ('127.0.0.1', 1)", ) in caplog.record_tuples - uri = parse_obj_as(AnyUrl, f"ws://{WS_TEST_HOST}:{WS_TEST_PORT}") + uri = TypeAdapter(AnyUrl).validate_python(f"ws://{WS_TEST_HOST}:{WS_TEST_PORT}") settings = WSDataBackendSettings(URI=uri) backend = AsyncWSDataBackend(settings) assert [_ async for _ in backend.read(raw_output=False)] == events diff --git a/tests/fixtures/backends.py b/tests/fixtures/backends.py index 727ffaa7e..ce4356e8b 100644 --- a/tests/fixtures/backends.py +++ b/tests/fixtures/backends.py @@ -18,7 +18,7 @@ import websockets from elasticsearch import BadRequestError, Elasticsearch from httpx import AsyncClient, ConnectError -from pydantic import AnyHttpUrl, parse_obj_as +from pydantic import AnyHttpUrl, TypeAdapter from pymongo import MongoClient from pymongo.errors import CollectionInvalid @@ -313,7 +313,7 @@ def _get_lrs_test_backend( "CONTENT_TYPE": "application/json", } settings = backend_class.settings_class( - BASE_URL=parse_obj_as(AnyHttpUrl, base_url), + BASE_URL=TypeAdapter(AnyHttpUrl).validate_python(base_url), USERNAME="user", PASSWORD="pass", HEADERS=LRSHeaders.model_validate(headers), diff --git a/tests/test_conf.py b/tests/test_conf.py index 39dec2ada..f08e47c03 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -3,7 +3,7 @@ from importlib import reload import pytest -from pydantic import ValidationError +from pydantic import TypeAdapter, ValidationError from ralph import conf from ralph.backends.data.es import ESDataBackend @@ -12,7 +12,6 @@ AuthBackends, CommaSeparatedTuple, Settings, - parse_obj_as, settings, ) from ralph.exceptions import ConfigurationException @@ -54,7 +53,7 @@ def test_conf_settings_field_value_priority(fs, monkeypatch): ) def test_conf_comma_separated_list_with_valid_values(value, expected, monkeypatch): """Test the CommaSeparatedTuple pydantic data type with valid values.""" - assert parse_obj_as(CommaSeparatedTuple, value) == expected + assert TypeAdapter(CommaSeparatedTuple).validate_python(value) == expected monkeypatch.setenv("RALPH_BACKENDS__DATA__ES__HOSTS", "".join(value)) assert ESDataBackend().settings.HOSTS == expected @@ -63,7 +62,7 @@ def test_conf_comma_separated_list_with_valid_values(value, expected, monkeypatc def test_conf_comma_separated_list_with_invalid_values(value): """Test the CommaSeparatedTuple pydantic data type with invalid values.""" with pytest.raises(ValidationError, match="2 validation errors for function-after"): - parse_obj_as(CommaSeparatedTuple, value) + TypeAdapter(CommaSeparatedTuple).validate_python(value) @pytest.mark.parametrize( @@ -80,13 +79,13 @@ def test_conf_comma_separated_list_with_invalid_values(value): def test_conf_auth_backend(value, is_valid, expected, monkeypatch): """Test the AuthBackends data type with valid and invalid values.""" if is_valid: - assert parse_obj_as(AuthBackends, value) == expected + assert TypeAdapter(AuthBackends).validate_python(value) == expected monkeypatch.setenv("RALPH_RUNSERVER_AUTH_BACKENDS", "".join(value)) reload(conf) assert conf.settings.RUNSERVER_AUTH_BACKENDS == expected else: with pytest.raises(ValueError, match="'notvalid' is not a valid AuthBackend"): - parse_obj_as(AuthBackends, value) + TypeAdapter(AuthBackends).validate_python(value) def test_conf_core_settings_should_impact_settings_defaults(monkeypatch):