Skip to content

Commit 2979317

Browse files
committed
tests on populate_by_name
1 parent f28c5f2 commit 2979317

File tree

2 files changed

+116
-4
lines changed

2 files changed

+116
-4
lines changed

packages/models-library/tests/test__pydantic_models.py

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
77
"""
88

9-
from typing import Any, Union, get_args, get_origin
9+
from typing import Any, Literal, Union, get_args, get_origin
1010

1111
import pytest
1212
from common_library.json_serialization import json_dumps
1313
from models_library.projects_nodes import InputTypes, OutputTypes
1414
from models_library.projects_nodes_io import SimCoreFileLink
15-
from pydantic import BaseModel, Field, TypeAdapter, ValidationError
15+
from models_library.utils.change_case import snake_to_camel
16+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, ValidationError
1617
from pydantic.types import Json
1718
from pydantic.version import version_short
1819

@@ -154,7 +155,7 @@ class Func(BaseModel):
154155
MINIMAL = 2 # <--- index of the example with the minimum required fields
155156
assert SimCoreFileLink in get_args(OutputTypes)
156157
example = SimCoreFileLink.model_validate(
157-
SimCoreFileLink.model_config["json_schema_extra"]["examples"][MINIMAL]
158+
SimCoreFileLink.model_json_schema()["examples"][MINIMAL]
158159
)
159160
model = Func.model_validate(
160161
{
@@ -211,3 +212,112 @@ class MyModel(BaseModel):
211212
data["nullable_required"] = None
212213
model = MyModel.model_validate(data)
213214
assert model.model_dump(exclude_unset=True) == data
215+
216+
217+
# BELOW some tests related to depreacated `populate_by_name` in pydantic v2.11+ !!
218+
#
219+
# https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.populate_by_name
220+
#
221+
# `populate_by_name` usage is not recommended in v2.11+ and will be deprecated in v3. Instead, you should use the validate_by_name configuration setting.
222+
# When validate_by_name=True and validate_by_alias=True, this is strictly equivalent to the previous behavior of populate_by_name=True.
223+
# In v2.11, we also introduced a validate_by_alias setting that introduces more fine grained control for validation behavior.
224+
# Here's how you might go about using the new settings to achieve the same behavior:
225+
#
226+
227+
228+
@pytest.mark.parametrize("extra", ["ignore", "allow", "forbid"])
229+
@pytest.mark.parametrize(
230+
"validate_by_alias, validate_by_name",
231+
[
232+
# NOTE: (False, False) is not allowed: at least one has to be True!
233+
# SEE https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_by_alias
234+
(False, True),
235+
(True, False),
236+
(True, True),
237+
],
238+
)
239+
def test_model_config_validate_by_alias_and_name(
240+
validate_by_alias: bool,
241+
validate_by_name: bool,
242+
extra: Literal["ignore", "allow", "forbid"],
243+
):
244+
class TestModel(BaseModel):
245+
snake_case: str | None = None
246+
247+
model_config = ConfigDict(
248+
validate_by_alias=validate_by_alias,
249+
validate_by_name=validate_by_name,
250+
extra=extra,
251+
alias_generator=snake_to_camel,
252+
)
253+
254+
assert TestModel.model_config.get("populate_by_name") is None
255+
assert TestModel.model_config.get("validate_by_alias") is validate_by_alias
256+
assert TestModel.model_config.get("validate_by_name") is validate_by_name
257+
assert TestModel.model_config.get("extra") == extra
258+
259+
if validate_by_alias is False:
260+
261+
if extra == "forbid":
262+
with pytest.raises(ValidationError):
263+
TestModel.model_validate({"snakeCase": "foo"})
264+
265+
elif extra == "ignore":
266+
model = TestModel.model_validate({"snakeCase": "foo"})
267+
assert model.snake_case is None
268+
assert model.model_dump() == {"snake_case": None}
269+
270+
elif extra == "allow":
271+
model = TestModel.model_validate({"snakeCase": "foo"})
272+
assert model.snake_case is None
273+
assert model.model_dump() == {"snake_case": None, "snakeCase": "foo"}
274+
275+
else:
276+
assert TestModel.model_validate({"snakeCase": "foo"}).snake_case == "foo"
277+
278+
if validate_by_name is False:
279+
if extra == "forbid":
280+
with pytest.raises(ValidationError):
281+
TestModel.model_validate({"snake_case": "foo"})
282+
283+
elif extra == "ignore":
284+
model = TestModel.model_validate({"snake_case": "foo"})
285+
assert model.snake_case is None
286+
assert model.model_dump() == {"snake_case": None}
287+
288+
elif extra == "allow":
289+
model = TestModel.model_validate({"snake_case": "foo"})
290+
assert model.snake_case is None
291+
assert model.model_dump() == {"snake_case": "foo"}
292+
else:
293+
assert TestModel.model_validate({"snake_case": "foo"}).snake_case == "foo"
294+
295+
296+
@pytest.mark.parametrize("populate_by_name", [True, False])
297+
def test_model_config_populate_by_name(populate_by_name: bool):
298+
# SEE https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.populate_by_name
299+
class TestModel(BaseModel):
300+
snake_case: str | None = None
301+
302+
model_config = ConfigDict(
303+
populate_by_name=populate_by_name,
304+
extra="forbid", # easier to check the effect of populate_by_name!
305+
alias_generator=snake_to_camel,
306+
)
307+
308+
# checks how they are set
309+
assert TestModel.model_config.get("populate_by_name") is populate_by_name
310+
assert TestModel.model_config.get("extra") == "forbid"
311+
312+
# NOTE how defaults work with populate_by_name!!
313+
assert TestModel.model_config.get("validate_by_name") == populate_by_name
314+
assert TestModel.model_config.get("validate_by_alias") is True # Default
315+
316+
# validate_by_alias BEHAVIUOR defaults to True
317+
TestModel.model_validate({"snakeCase": "foo"})
318+
319+
if populate_by_name:
320+
assert TestModel.model_validate({"snake_case": "foo"}).snake_case == "foo"
321+
else:
322+
with pytest.raises(ValidationError):
323+
TestModel.model_validate({"snake_case": "foo"})

services/web/server/tests/unit/with_dbs/03/trash/test_trash_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
from servicelib.aiohttp import status
2222
from simcore_service_webserver.db.models import UserRole
2323
from simcore_service_webserver.projects import _trash_service
24-
from simcore_service_webserver.projects.models import ProjectDict
24+
from simcore_service_webserver.projects.models import (
25+
ProjectDict,
26+
)
2527
from simcore_service_webserver.trash import trash_service
2628

2729

0 commit comments

Comments
 (0)