89 prev_state: _ConfigDict
95 previous: _ChangelogEntry |
None,
99 prev_state = previous.state
105 state=prev_state.copy(),
106 prev_state=prev_state,
110 def has_changes(self) -> bool:
113 def update(self, values: _ConfigDict):
114 for key, value
in values.items():
115 if value == self.prev_state.get(key, _MISSING):
119 self.state.update(values)
122@dataclasses.dataclass(frozen=True)
134 timestamp: datetime.datetime
135 committed_entries: list[_ChangelogEntry]
136 staged_entry: _ChangelogEntry
139 self.
timestamp = datetime.datetime.fromtimestamp(
141 datetime.timezone.utc,
149 def service_timestamp(self) -> str:
150 return self.
timestamp.strftime(
'%Y-%m-%dT%H:%M:%SZ')
152 def next_timestamp(self) -> str:
153 self.
timestamp += datetime.timedelta(seconds=1)
157 """Commit staged changed if any and return last committed entry."""
167 def get_updated_since(
169 config_dict: _ConfigDict,
171 ids: list[str] |
None =
None,
176 config_dict = {name: config_dict[name]
for name
in ids
if name
in config_dict}
177 removed = [name
for name
in removed
if name
in ids]
180 kill_switches_disabled = []
181 for name, config_entry
in config_dict.items():
182 values[name] = config_entry.value
183 if config_entry.static_default_preferred:
184 kill_switches_disabled.append(name)
187 timestamp=entry.timestamp,
190 kill_switches_disabled=kill_switches_disabled,
193 def _get_updated_since(
195 config_dict: _ConfigDict,
197 ) -> tuple[_ConfigDict, list[str]]:
198 if not updated_since:
199 return config_dict, []
201 last_known_state = {}
203 if entry.timestamp > updated_since:
204 dirty_keys.update(entry.dirty_keys)
206 if entry.timestamp == updated_since:
207 last_known_state = entry.state
212 for key
in dirty_keys:
213 config_entry = config_dict.get(key, _REMOVE_KEY)
214 if last_known_state.get(key, _MISSING) != config_entry:
215 if config_entry
is _REMOVE_KEY:
218 result[key] = config_entry
219 return result, removed
221 def add_entries(self, config_dict: _ConfigDict) ->
None:
224 @contextlib.contextmanager
225 def rollback(self, defaults: ConfigValuesDict) -> Iterator[
None]:
231 def _do_rollback(self, defaults: ConfigValuesDict) ->
None:
237 maybe_dirty.update(entry.dirty_keys)
240 last_state = last.state
241 config_dict = _create_config_dict(defaults)
244 for key
in maybe_dirty:
245 original = config_dict.get(key, _REMOVE_KEY)
246 if last_state[key] != original:
248 reverted[key] = original
251 timestamp=last.timestamp,
253 dirty_keys=dirty_keys,
259 dirty_keys=dirty_keys.copy(),
261 prev_state=entry.state,
267 @brief Simple dynamic config backend.
269 @see @ref pytest_userver.plugins.dynamic_config.dynamic_config "dynamic_config"
275 initial_values: ConfigValuesDict,
276 defaults: ConfigValuesDict |
None,
277 config_cache_components: Iterable[str],
279 changelog: _Changelog,
281 self.
_values = initial_values.copy()
290 def set_values(self, values: ConfigValuesDict) ->
None:
293 def set_values_unsafe(self, values: ConfigValuesDict) ->
None:
298 config_dict = _create_config_dict(values)
302 def set(self, **values) -> None:
305 def switch_to_static_default(self, *keys: str) ->
None:
309 config_dict = _create_config_dict(
310 values={key: self.
_values.get(key,
None)
for key
in keys},
311 kill_switches_disabled=set(keys),
316 def switch_to_dynamic_value(self, *keys: str) ->
None:
320 config_dict = _create_config_dict(values={key: self.
_values[key]
for key
in keys
if key
in self.
_values})
324 def get_values_unsafe(self) -> ConfigValuesDict:
327 def get_kill_switches_disabled_unsafe(self) -> Set[str]:
330 def get(self, key: str, default: Any =
None) -> Any:
332 return copy.deepcopy(self.
_values[key])
334 return copy.deepcopy(self.
_defaults[key])
335 if default
is not None:
339 f
'Defaults for config {key!r} have not yet been fetched '
340 'from the service. Options:\n'
341 '1. add a dependency on service_client in your fixture;\n'
342 '2. pass `default` parameter to `dynamic_config.get`',
346 def remove_values(self, keys: Iterable[str]) ->
None:
347 extra_keys = set(keys).difference(self.
_values.keys())
350 f
'Attempting to remove nonexistent configs: {extra_keys}',
356 self.
_changelog.add_entries({key: _REMOVE_KEY
for key
in keys})
359 def remove(self, key: str) ->
None:
362 @contextlib.contextmanager
363 def modify(self, key: str) -> Any:
364 value = self.
get(key)
368 @contextlib.contextmanager
371 *keys: tuple[str, ...],
372 ) -> tuple[Any, ...]:
373 values = tuple(self.
get(key)
for key
in keys)
375 self.
set_values(dict(zip(keys, values, strict=
True)))
377 def _sync_with_service(self) -> None: