44"""Management of configuration."""
55
66import asyncio
7+ import logging
78import pathlib
89from collections .abc import Mapping , Sequence
910from datetime import timedelta
1011from typing import Any , Final
1112
1213from frequenz .channels import Broadcast , Receiver
14+ from frequenz .channels .experimental import WithPrevious
1315
1416from ._managing_actor import ConfigManagingActor
1517
18+ _logger = logging .getLogger (__name__ )
19+
1620
1721class ConfigManager :
1822 """A manager for configuration files.
@@ -99,13 +103,24 @@ async def new_receiver( # pylint: disable=too-many-arguments # noqa: DOC502
99103 self ,
100104 * ,
101105 wait_for_first : bool = False ,
106+ skip_unchanged : bool = True ,
102107 ) -> Receiver [Mapping [str , Any ] | None ]:
103108 """Create a new receiver for the configuration.
104109
110+ This method has a lot of features and functionalities to make it easier to
111+ receive configurations.
112+
105113 Note:
106114 If there is a burst of configuration updates, the receiver will only
107115 receive the last configuration, older configurations will be ignored.
108116
117+ ### Skipping superfluous updates
118+
119+ If `skip_unchanged` is set to `True`, then a configuration that didn't change
120+ compared to the last one received will be ignored and not sent to the receiver.
121+ The comparison is done using the *raw* `dict` to determine if the configuration
122+ has changed.
123+
109124 ### Waiting for the first configuration
110125
111126 If `wait_for_first` is `True`, the receiver will wait for the first
@@ -129,6 +144,8 @@ async def new_receiver( # pylint: disable=too-many-arguments # noqa: DOC502
129144 time, a timeout error will be raised. If receiving was successful, the
130145 first configuration can be simply retrieved by calling
131146 [`consume()`][frequenz.channels.Receiver.consume] on the receiver.
147+ skip_unchanged: Whether to skip sending the configuration if it hasn't
148+ changed compared to the last one received.
132149
133150 Returns:
134151 The receiver for the configuration.
@@ -140,8 +157,22 @@ async def new_receiver( # pylint: disable=too-many-arguments # noqa: DOC502
140157 recv_name = f"{ self } _receiver"
141158 receiver = self .config_channel .new_receiver (name = recv_name , limit = 1 )
142159
160+ if skip_unchanged :
161+ receiver = receiver .filter (WithPrevious (not_equal_with_logging ))
162+
143163 if wait_for_first :
144164 async with asyncio .timeout (self .wait_for_first_timeout .total_seconds ()):
145165 await receiver .ready ()
146166
147167 return receiver
168+
169+
170+ def not_equal_with_logging (
171+ old_config : Mapping [str , Any ], new_config : Mapping [str , Any ]
172+ ) -> bool :
173+ """Return whether the two mappings are not equal, logging if they are the same."""
174+ if old_config == new_config :
175+ _logger .info ("Configuration has not changed, skipping update" )
176+ _logger .debug ("Old configuration being kept: %r" , old_config )
177+ return False
178+ return True
0 commit comments