Skip to content

Commit

Permalink
fix: remove workarounds for generic validation
Browse files Browse the repository at this point in the history
Obviated by pydantic/pydantic#10666, which
does the Right Thing with instances of unparameterized generics.
  • Loading branch information
jvansanten committed Nov 22, 2024
1 parent e429633 commit 5e21ef4
Show file tree
Hide file tree
Showing 6 changed files with 17 additions and 42 deletions.
16 changes: 0 additions & 16 deletions ampel/base/AmpelBaseModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
# Last Modified Date: 05.01.2022
# Last Modified By: valery brinnel <firstname.lastname@gmail.com>

import warnings
from types import UnionType
from typing import TYPE_CHECKING, Any, TypeAlias, Union, get_args, get_origin

Expand Down Expand Up @@ -50,21 +49,6 @@ def __init_subclass__(cls, *args, **kwargs) -> None:
and NoneType in get_args(v)
):
setattr(cls, k, None)
# add generic args to defaults if missing
elif (
k in cls.__dict__
and safe_issubclass(v, AmpelBaseModel)
and v.get_model_origin() is type(cls.__dict__[k])
and v.get_model_args() and not cls.__dict__[k].get_model_args()
):
warnings.warn(
DeprecationWarning(
f"field {k} declared as {v}, but default has type {type(cls.__dict__[k])}"
" Adding generic args to default, but this will be an error in the future."
),
stacklevel=1
)
setattr(cls, k, v.model_validate(cls.__dict__[k].model_dump()))
super().__init_subclass__(*args, **kwargs)

@classmethod
Expand Down
20 changes: 2 additions & 18 deletions ampel/base/AmpelUnit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
# Last Modified Date: 09.01.2022
# Last Modified By: valery brinnel <firstname.lastname@gmail.com>

import warnings
from functools import partial
from types import MemberDescriptorType, UnionType
from typing import TYPE_CHECKING, Any, ClassVar, Union, get_args, get_origin

from pydantic import BaseModel, ValidationError, create_model

from ampel.base.AmpelBaseModel import AmpelBaseModel, safe_issubclass
from ampel.base.AmpelBaseModel import AmpelBaseModel
from ampel.secret.Secret import Secret
from ampel.types import TRACELESS, Traceless

Expand Down Expand Up @@ -72,22 +71,7 @@ def __init_subclass__(cls, *args, **kwargs) -> None:
if k in cls._slot_defaults:
joined_defaults[k] = cls._slot_defaults[k]
continue
# parameterized generic with unparameterized default
if (
safe_issubclass(v, AmpelBaseModel)
and v.get_model_origin() is type(defs[k])
and v.get_model_args() and not defs[k].get_model_args()
):
warnings.warn(
DeprecationWarning(
f"field {k} declared as {v}, but default has type {type(defs[k])}"
" Adding generic args to default, but this will be an error in the future."
),
stacklevel=1
)
joined_defaults[k] = v.model_validate(defs[k].model_dump())
else:
joined_defaults[k] = base.__dict__[k]
joined_defaults[k] = base.__dict__[k]
# if None | with no default
elif get_origin(v) in (Union, UnionType) and NoneType in get_args(v) and k not in joined_defaults:
joined_defaults[k] = None
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ packages = [{include = "ampel"}]

[tool.poetry.dependencies]
python = "^3.10"
pydantic = "^2.7"
pydantic = "^2.10"
xxhash = "^3.0.0"
PyYAML = "^6.0.0"
ujson = "^5.1.0"
Expand Down
4 changes: 0 additions & 4 deletions tests/test_AmpelUnit.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ class Unit(AmpelUnit):
}


@pytest.mark.filterwarnings("ignore:field .* declared as:DeprecationWarning")
def test_secret_without_type():
class UnitWithSecret(AmpelUnit):
secret: NamedSecret[str] = NamedSecret(label="foo")
Expand All @@ -109,9 +108,6 @@ class UnitWithSecret(AmpelUnit):
other: Sequence[int] = [1]

assert UnitWithSecret().secret.get_model_args() == (str,)
assert UnitWithSecret._defaults["secret"].get_model_args() == (
str,
), "parameteized "


def test_slots():
Expand Down
15 changes: 13 additions & 2 deletions tests/test_AmpelVault.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,20 @@ class HasSecret(AmpelBaseModel):


def test_secret_validation() -> None:
HasSecret(secret=NamedSecret[str](label="foo"))
"""Secret fields can be initialized with and without type hints, but validate if fields are provided"""
secret = HasSecret(secret=NamedSecret[str](label="foo", value="bar")).secret
assert HasSecret(secret=NamedSecret(label="foo", value="bar")).secret == secret
assert HasSecret(**{"secret": {"label": "foo", "value": "bar"}}).secret == secret # type: ignore[arg-type]
# type parameter defaults to Any
NamedSecret(label="foo", value=1)
# validation fails with explicit type hint
with pytest.raises(ValidationError):
HasSecret(secret=NamedSecret(label="foo"))
NamedSecret[str](label="foo", value=1) # type: ignore[arg-type]
# validation succeeds with correct type hint
int_secret = NamedSecret[int](label="foo", value=1)
# but passing the value to a model field will fail
with pytest.raises(ValidationError):
HasSecret(secret=int_secret) # type: ignore[arg-type]


def test_secret_resolution() -> None:
Expand Down

0 comments on commit 5e21ef4

Please sign in to comment.