Skip to content

Commit 1be3d2c

Browse files
committed
Require using built-in dataclasses in load_config()
This is just the last step to be able to enforce via the type system that only built-in dataclasses are used. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent e311276 commit 1be3d2c

File tree

1 file changed

+20
-5
lines changed

1 file changed

+20
-5
lines changed

src/frequenz/sdk/config/_util.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,37 @@
44
"""Utilities to deal with configuration."""
55

66
from collections.abc import Mapping
7-
from typing import Any, TypeVar, cast
7+
from typing import Any, ClassVar, Protocol, TypeVar, cast
88

99
from marshmallow import Schema
1010
from marshmallow_dataclass import class_schema
1111

12-
T = TypeVar("T")
12+
13+
# This is a hack that relies on identifying dataclasses by looking into an undocumented
14+
# property of dataclasses[1], so it might break in the future. Nevertheless, it seems to
15+
# be widely used in the community, for example `mypy` and `pyright` seem to rely on
16+
# it[2].
17+
#
18+
# [1]: https://github.com/python/mypy/issues/15974#issuecomment-1694781006
19+
# [2]: https://github.com/python/mypy/issues/15974#issuecomment-1694993493
20+
class Dataclass(Protocol):
21+
"""A protocol for dataclasses."""
22+
23+
__dataclass_fields__: ClassVar[dict[str, Any]]
24+
"""The fields of the dataclass."""
25+
26+
27+
DataclassT = TypeVar("DataclassT", bound=Dataclass)
1328
"""Type variable for configuration classes."""
1429

1530

1631
def load_config(
17-
cls: type[T],
32+
cls: type[DataclassT],
1833
config: Mapping[str, Any],
1934
/,
2035
base_schema: type[Schema] | None = None,
2136
**marshmallow_load_kwargs: Any,
22-
) -> T:
37+
) -> DataclassT:
2338
"""Load a configuration from a dictionary into an instance of a configuration class.
2439
2540
The configuration class is expected to be a [`dataclasses.dataclass`][], which is
@@ -56,4 +71,4 @@ def load_config(
5671
instance = class_schema(cls, base_schema)().load(config, **marshmallow_load_kwargs)
5772
# We need to cast because `.load()` comes from marshmallow and doesn't know which
5873
# type is returned.
59-
return cast(T, instance)
74+
return cast(DataclassT, instance)

0 commit comments

Comments
 (0)