Skip to content

Commit

Permalink
Restore support for marshmallow_dataclass (#1106)
Browse files Browse the repository at this point in the history
It seems to work with `class_shema()` and new Dataclass protocol
recognize it as dataclass.
  • Loading branch information
ela-kotulska-frequenz authored Nov 18, 2024
2 parents 4484c2b + 123258a commit 7d1e106
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
2 changes: 0 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

## Upgrading

- `frequenz.sdk.config.load_config()` doesn't accept classes decorated with `marshmallow_dataclass.dataclass` anymore. You should use the built-in `dataclasses.dataclass` directly instead, no other changes should be needed, the metadata in the `dataclass` fields will still be used.

## New Features


Expand Down
50 changes: 40 additions & 10 deletions tests/config/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any

import marshmallow
import marshmallow_dataclass
import pytest
from pytest_mock import MockerFixture

Expand All @@ -21,26 +22,55 @@ class SimpleConfig:
value: int


def test_load_config_dataclass() -> None:
@marshmallow_dataclass.dataclass
class MmSimpleConfig:
"""A simple marshmallow_dataclass configuration class for testing."""

name: str = dataclasses.field(metadata={"validate": lambda s: s.startswith("test")})
value: int


@pytest.mark.parametrize(
"config_class",
[SimpleConfig, MmSimpleConfig],
ids=["dataclass", "marshmallow_dataclass"],
)
def test_load_config_dataclass(
config_class: type[SimpleConfig] | type[MmSimpleConfig],
) -> None:
"""Test that load_config loads a configuration into a configuration class."""
config: dict[str, Any] = {"name": "test", "value": 42}

loaded_config = load_config(SimpleConfig, config)
assert loaded_config == SimpleConfig(name="test", value=42)
loaded_config = load_config(config_class, config)
assert loaded_config == config_class(name="test", value=42)

config["name"] = "not test"
with pytest.raises(marshmallow.ValidationError):
_ = load_config(SimpleConfig, config)
_ = load_config(config_class, config)


def test_load_config_load_None() -> None:
@pytest.mark.parametrize(
"config_class",
[SimpleConfig, MmSimpleConfig],
ids=["dataclass", "marshmallow_dataclass"],
)
def test_load_config_load_None(
config_class: type[SimpleConfig] | type[MmSimpleConfig],
) -> None:
"""Test that load_config raises ValidationError if the configuration is None."""
config: dict[str, Any] = {}
with pytest.raises(marshmallow.ValidationError):
_ = load_config(SimpleConfig, config.get("loggers", None))
_ = load_config(config_class, config.get("loggers", None))


def test_load_config_with_base_schema() -> None:
@pytest.mark.parametrize(
"config_class",
[SimpleConfig, MmSimpleConfig],
ids=["dataclass", "marshmallow_dataclass"],
)
def test_load_config_with_base_schema(
config_class: type[SimpleConfig] | type[MmSimpleConfig],
) -> None:
"""Test that load_config loads a configuration using a base schema."""

class _MyBaseSchema(marshmallow.Schema):
Expand All @@ -53,11 +83,11 @@ class Meta:

config: dict[str, Any] = {"name": "test", "value": 42, "extra": "extra"}

loaded_config = load_config(SimpleConfig, config, base_schema=_MyBaseSchema)
assert loaded_config == SimpleConfig(name="test", value=42)
loaded_config = load_config(config_class, config, base_schema=_MyBaseSchema)
assert loaded_config == config_class(name="test", value=42)

with pytest.raises(marshmallow.ValidationError):
_ = load_config(SimpleConfig, config)
_ = load_config(config_class, config)


def test_load_config_type_hints(mocker: MockerFixture) -> None:
Expand Down

0 comments on commit 7d1e106

Please sign in to comment.