|
3 | 3 |
|
4 | 4 | """Management of configuration.""" |
5 | 5 |
|
| 6 | +import logging |
6 | 7 | import pathlib |
7 | 8 | from collections.abc import Mapping, Sequence |
8 | 9 | from datetime import timedelta |
9 | 10 | from typing import Any, Final |
10 | 11 |
|
11 | 12 | from frequenz.channels import Broadcast, Receiver |
| 13 | +from frequenz.channels.experimental import WithPrevious |
12 | 14 | from typing_extensions import override |
13 | 15 |
|
14 | 16 | from ..actor._background_service import BackgroundService |
15 | 17 | from ._managing_actor import ConfigManagingActor |
16 | 18 |
|
| 19 | +_logger = logging.getLogger(__name__) |
| 20 | + |
17 | 21 |
|
18 | 22 | class ConfigManager(BackgroundService): |
19 | 23 | """A manager for configuration files. |
@@ -100,19 +104,55 @@ def __repr__(self) -> str: |
100 | 104 | """Return a string representation of this config manager.""" |
101 | 105 | return f"config_channel={self.config_channel!r}, " f"actor={self.actor!r}>" |
102 | 106 |
|
103 | | - async def new_receiver(self) -> Receiver[Mapping[str, Any] | None]: |
| 107 | + def new_receiver( |
| 108 | + self, |
| 109 | + *, |
| 110 | + skip_unchanged: bool = True, |
| 111 | + ) -> Receiver[Mapping[str, Any] | None]: |
104 | 112 | """Create a new receiver for the configuration. |
105 | 113 |
|
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. |
| 114 | + This method has a lot of features and functionalities to make it easier to |
| 115 | + receive configurations, but it also imposes some restrictions on how the |
| 116 | + configurations are received. If you need more control over the configuration |
| 117 | + receiver, you can create a receiver directly using |
| 118 | + [`config_channel.new_receiver()`][frequenz.sdk.config.ConfigManager.config_channel]. |
| 119 | +
|
| 120 | + ### Skipping superfluous updates |
| 121 | +
|
| 122 | + If there is a burst of configuration updates, the receiver will only receive the |
| 123 | + last configuration, older configurations will be ignored. |
| 124 | +
|
| 125 | + If `skip_unchanged` is set to `True`, then a configuration that didn't change |
| 126 | + compared to the last one received will be ignored and not sent to the receiver. |
| 127 | + The comparison is done using the *raw* `dict` to determine if the configuration |
| 128 | + has changed. |
109 | 129 |
|
110 | 130 | Example: |
111 | 131 | ```python |
112 | 132 | # TODO: Add Example |
113 | 133 | ``` |
114 | 134 |
|
| 135 | + Args: |
| 136 | + skip_unchanged: Whether to skip sending the configuration if it hasn't |
| 137 | + changed compared to the last one received. |
| 138 | +
|
115 | 139 | Returns: |
116 | 140 | The receiver for the configuration. |
117 | 141 | """ |
118 | | - return self.config_channel.new_receiver(name=str(self), limit=1) |
| 142 | + receiver = self.config_channel.new_receiver(name=str(self), limit=1) |
| 143 | + |
| 144 | + if skip_unchanged: |
| 145 | + receiver = receiver.filter(WithPrevious(not_equal_with_logging)) |
| 146 | + |
| 147 | + return receiver |
| 148 | + |
| 149 | + |
| 150 | +def not_equal_with_logging( |
| 151 | + old_config: Mapping[str, Any], new_config: Mapping[str, Any] |
| 152 | +) -> bool: |
| 153 | + """Return whether the two mappings are not equal, logging if they are the same.""" |
| 154 | + if old_config == new_config: |
| 155 | + _logger.info("Configuration has not changed, skipping update") |
| 156 | + _logger.debug("Old configuration being kept: %r", old_config) |
| 157 | + return False |
| 158 | + return True |
0 commit comments