Skip to content

Commit 9cc55cc

Browse files
committed
Add a ConfigManager class
This class instantiates and starts the `ConfigManagingActor` and creates the channel needed to communicate with it. It offers a convenience method to get receivers to receive configuration updates. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent ade18ea commit 9cc55cc

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

src/frequenz/sdk/config/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# License: MIT
22
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
33

4-
"""Read and update config variables."""
4+
"""Configuration management."""
55

66
from ._logging_actor import LoggerConfig, LoggingConfig, LoggingConfigUpdatingActor
7+
from ._manager import ConfigManager
78
from ._managing_actor import ConfigManagingActor
89
from ._util import load_config
910

1011
__all__ = [
12+
"ConfigManager",
1113
"ConfigManagingActor",
1214
"LoggerConfig",
1315
"LoggingConfig",
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Management of configuration."""
5+
6+
import pathlib
7+
from collections.abc import Mapping, Sequence
8+
from datetime import timedelta
9+
from typing import Any, Final
10+
11+
from frequenz.channels import Broadcast, Receiver
12+
from typing_extensions import override
13+
14+
from ..actor._background_service import BackgroundService
15+
from ._managing_actor import ConfigManagingActor
16+
17+
18+
class ConfigManager(BackgroundService):
19+
"""A manager for configuration files.
20+
21+
This class reads configuration files and sends the configuration to the receivers,
22+
providing optional configuration key filtering and schema validation.
23+
"""
24+
25+
def __init__( # pylint: disable=too-many-arguments
26+
self,
27+
config_paths: Sequence[pathlib.Path],
28+
/,
29+
*,
30+
force_polling: bool = True,
31+
name: str | None = None,
32+
polling_interval: timedelta = timedelta(seconds=5),
33+
) -> None:
34+
"""Initialize this config manager.
35+
36+
Args:
37+
config_paths: The paths to the TOML files with the configuration. Order
38+
matters, as the configuration will be read and updated in the order
39+
of the paths, so the last path will override the configuration set by
40+
the previous paths. Dict keys will be merged recursively, but other
41+
objects (like lists) will be replaced by the value in the last path.
42+
force_polling: Whether to force file polling to check for changes.
43+
name: A name to use when creating actors. If `None`, `str(id(self))` will
44+
be used. This is used mostly for debugging purposes.
45+
polling_interval: The interval to poll for changes. Only relevant if
46+
polling is enabled.
47+
"""
48+
super().__init__(name=name)
49+
50+
self.config_channel: Final[Broadcast[Mapping[str, Any]]] = Broadcast(
51+
name=f"{self}_config", resend_latest=True
52+
)
53+
"""The broadcast channel for the configuration."""
54+
55+
self.actor: Final[ConfigManagingActor] = ConfigManagingActor(
56+
config_paths,
57+
self.config_channel.new_sender(),
58+
name=self.name,
59+
force_polling=force_polling,
60+
polling_interval=polling_interval,
61+
)
62+
"""The actor that manages the configuration."""
63+
64+
@override
65+
def start(self) -> None:
66+
"""Start this config manager."""
67+
self.actor.start()
68+
69+
@property
70+
@override
71+
def is_running(self) -> bool:
72+
"""Whether this config manager is running."""
73+
return self.actor.is_running
74+
75+
@override
76+
def cancel(self, msg: str | None = None) -> None:
77+
"""Cancel all running tasks and actors spawned by this config manager.
78+
79+
Args:
80+
msg: The message to be passed to the tasks being cancelled.
81+
"""
82+
self.actor.cancel(msg)
83+
84+
# We need the noqa because the `BaseExceptionGroup` is raised indirectly.
85+
@override
86+
async def wait(self) -> None: # noqa: DOC502
87+
"""Wait this config manager to finish.
88+
89+
Wait until all tasks and actors are finished.
90+
91+
Raises:
92+
BaseExceptionGroup: If any of the tasks spawned by this service raised an
93+
exception (`CancelError` is not considered an error and not returned in
94+
the exception group).
95+
"""
96+
await self.actor
97+
98+
@override
99+
def __repr__(self) -> str:
100+
"""Return a string representation of this config manager."""
101+
return f"config_channel={self.config_channel!r}, " f"actor={self.actor!r}>"
102+
103+
async def new_receiver(self) -> Receiver[Mapping[str, Any] | None]:
104+
"""Create a new receiver for the configuration.
105+
106+
Note:
107+
If there is a burst of configuration updates, the receiver will only
108+
receive the last configuration, older configurations will be ignored.
109+
110+
Example:
111+
```python
112+
# TODO: Add Example
113+
```
114+
115+
Returns:
116+
The receiver for the configuration.
117+
"""
118+
return self.config_channel.new_receiver(name=str(self), limit=1)

0 commit comments

Comments
 (0)