Coverage for .tox/cov/lib/python3.11/site-packages/confattr/config.py: 100%
206 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-12 20:24 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-12 20:24 +0200
1#!./runmodule.sh
3import builtins
4import enum
5import typing
6from collections.abc import Iterable, Iterator, Container, Sequence, Callable
8if typing.TYPE_CHECKING:
9 from typing_extensions import Self
11from .formatters import AbstractFormatter, CopyableAbstractFormatter, Primitive, List, Set, Dict, format_primitive_value
14#: An identifier to specify which value of a :class:`~confattr.config.MultiConfig` or :class:`~confattr.config.MultiDictConfig` should be used for a certain object.
15ConfigId = typing.NewType('ConfigId', str)
17T_KEY = typing.TypeVar('T_KEY')
18T = typing.TypeVar('T')
21class Config(typing.Generic[T]):
23 '''
24 Each instance of this class represents a setting which can be changed in a config file.
26 This class implements the `descriptor protocol <https://docs.python.org/3/reference/datamodel.html#implementing-descriptors>`__ to return :attr:`~confattr.config.Config.value` if an instance of this class is accessed as an instance attribute.
27 If you want to get this object you need to access it as a class attribute.
28 '''
30 #: A mapping of all :class:`~confattr.config.Config` instances. The key in the mapping is the :attr:`~confattr.config.Config.key` attribute. The value is the :class:`~confattr.config.Config` instance. New :class:`~confattr.config.Config` instances add themselves automatically in their constructor.
31 instances: 'dict[str, Config[typing.Any]]' = {}
33 default_config_id = ConfigId('general')
35 #: The value of this setting.
36 value: 'T'
38 #: Information about data type, unit and allowed values for :attr:`~confattr.config.Config.value` and methods how to parse, format and complete it.
39 type: 'AbstractFormatter[T]'
41 #: A description of this setting or a description for each allowed value.
42 help: 'str|dict[T, str]|None'
45 def __init__(self,
46 key: str,
47 default: T, *,
48 type: 'AbstractFormatter[T]|None' = None,
49 unit: 'str|None' = None,
50 allowed_values: 'Sequence[T]|dict[str, T]|None' = None,
51 help: 'str|dict[T, str]|None' = None,
52 parent: 'DictConfig[typing.Any, T]|None' = None,
53 ):
54 '''
55 :param key: The name of this setting in the config file
56 :param default: The default value of this setting
57 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`~confattr.config.Config.default`. But if :paramref:`~confattr.config.Config.default` is an empty list the item type cannot be determined automatically so that this argument must be passed explicitly. This also gives the possibility to format a standard type differently e.g. as :class:`~confattr.formatters.Hex`. It is not permissible to reuse the same object for different settings, otherwise :meth:`AbstractFormatter.set_config_key() <confattr.formatters.AbstractFormatter.set_config_key>` will throw an exception.
58 :param unit: The unit of an int or float value (only if type is None)
59 :param allowed_values: The possible values this setting can have. Values read from a config file or an environment variable are checked against this. The :paramref:`~confattr.config.Config.default` value is *not* checked. (Only if type is None.)
60 :param help: A description of this setting
61 :param parent: Applies only if this is part of a :class:`~confattr.config.DictConfig`
63 :obj:`~confattr.config.T` can be one of:
64 * :class:`str`
65 * :class:`int`
66 * :class:`float`
67 * :class:`bool`
68 * a subclass of :class:`enum.Enum` (the value used in the config file is the name in lower case letters with hyphens instead of underscores)
69 * a class where :meth:`~object.__str__` returns a string representation which can be passed to the constructor to create an equal object. \
70 A help which is written to the config file must be provided as a str in the class attribute :attr:`~confattr.types.AbstractType.help` or by adding it to :attr:`Primitive.help_dict <confattr.formatters.Primitive.help_dict>`. \
71 If that class has a str attribute :attr:`~confattr.types.AbstractType.type_name` this is used instead of the class name inside of config file.
72 * a :class:`list` of any of the afore mentioned data types. The list may not be empty when it is passed to this constructor so that the item type can be derived but it can be emptied immediately afterwards. (The type of the items is not dynamically enforced—that's the job of a static type checker—but the type is mentioned in the help.)
74 :raises ValueError: if key is not unique
75 :raises TypeError: if :paramref:`~confattr.config.Config.default` is an empty list/set because the first element is used to infer the data type to which a value given in a config file is converted
76 :raises TypeError: if this setting is a number or a list of numbers and :paramref:`~confattr.config.Config.unit` is not given
77 '''
78 if type is None:
79 if isinstance(default, list):
80 if not default:
81 raise TypeError('I cannot infer the item type from an empty list. Please pass an argument to the type parameter.')
82 item_type: 'builtins.type[T]' = builtins.type(default[0])
83 type = typing.cast('AbstractFormatter[T]', List(item_type=Primitive(item_type, allowed_values=allowed_values, unit=unit)))
84 elif isinstance(default, set):
85 if not default:
86 raise TypeError('I cannot infer the item type from an empty set. Please pass an argument to the type parameter.')
87 item_type = builtins.type(next(iter(default)))
88 type = typing.cast('AbstractFormatter[T]', Set(item_type=Primitive(item_type, allowed_values=allowed_values, unit=unit)))
89 elif isinstance(default, dict):
90 if not default:
91 raise TypeError('I cannot infer the key and value types from an empty dict. Please pass an argument to the type parameter.')
92 some_key, some_value = next(iter(default.items()))
93 key_type = Primitive(builtins.type(some_key))
94 val_type = Primitive(builtins.type(some_value), allowed_values=allowed_values, unit=unit)
95 type = typing.cast('AbstractFormatter[T]', Dict(key_type, val_type))
96 else:
97 type = Primitive(builtins.type(default), allowed_values=allowed_values, unit=unit)
98 else:
99 if unit is not None:
100 raise TypeError("The keyword argument 'unit' is not supported if 'type' is given. Pass it to the type instead.")
101 if allowed_values is not None:
102 raise TypeError("The keyword argument 'allowed_values' is not supported if 'type' is given. Pass it to the type instead.")
104 type.set_config_key(key)
106 self._key = key
107 self.value = default
108 self.type = type
109 self.help = help
110 self.parent = parent
112 cls = builtins.type(self)
113 if key in cls.instances:
114 raise ValueError(f'duplicate config key {key!r}')
115 cls.instances[key] = self
117 @property
118 def key(self) -> str:
119 '''The name of this setting which is used in the config file. This must be unique.'''
120 return self._key
122 @key.setter
123 def key(self, key: str) -> None:
124 if key in self.instances:
125 raise ValueError(f'duplicate config key {key!r}')
126 del self.instances[self._key]
127 self._key = key
128 self.type.config_key = key
129 self.instances[key] = self
132 @typing.overload
133 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self':
134 pass
136 @typing.overload
137 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> T:
138 pass
140 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'T|Self':
141 if instance is None:
142 return self
144 return self.value
146 def __set__(self: 'Config[T]', instance: typing.Any, value: T) -> None:
147 self.value = value
149 def __repr__(self) -> str:
150 return '%s(%s, ...)' % (type(self).__name__, ', '.join(repr(a) for a in (self.key, self.value)))
152 def set_value(self: 'Config[T]', config_id: 'ConfigId|None', value: T) -> None:
153 '''
154 This method is just to provide a common interface for :class:`~confattr.config.Config` and :class:`~confattr.config.MultiConfig`.
155 If you know that you are dealing with a normal :class:`~confattr.config.Config` you can set :attr:`~confattr.config.Config.value` directly.
156 '''
157 if config_id is None:
158 config_id = self.default_config_id
159 if config_id != self.default_config_id:
160 raise ValueError(f'{self.key} cannot be set for specific groups, config_id must be the default {self.default_config_id!r} not {config_id!r}')
161 self.value = value
163 def wants_to_be_exported(self) -> bool:
164 return True
166 def get_value(self, config_id: 'ConfigId|None') -> T:
167 '''
168 :return: :attr:`~confattr.config.Config.value`
170 This getter is only to have a common interface for :class:`~confattr.config.Config` and :class:`~confattr.config.MultiConfig`
171 '''
172 return self.value
174class ExplicitConfig(Config[T]):
176 '''
177 A setting without a default value which requires the user to explicitly set a value in the config file.
178 '''
180 def __init__(self,
181 key: str,
182 type: 'AbstractFormatter[T]|type[T]|None' = None, *,
183 unit: 'str|None' = None,
184 allowed_values: 'Sequence[T]|dict[str, T]|None' = None,
185 help: 'str|dict[T, str]|None' = None,
186 parent: 'DictConfig[typing.Any, T]|None' = None,
187 ):
188 '''
189 :param key: The name of this setting in the config file
190 :param type: How to parse, format and complete a value. Any class which can be passed to :class:`~confattr.formatters.Primitive` or an object of a subclass of :class:`~confattr.formatters.AbstractFormatter`.
191 :param unit: The unit of an int or float value (only if type is not an :class:`~confattr.formatters.AbstractFormatter`)
192 :param allowed_values: The possible values this setting can have. Values read from a config file or an environment variable are checked against this. The :paramref:`~confattr.config.Config.default` value is *not* checked. (Only if type is not an :class:`~confattr.formatters.AbstractFormatter`.)
193 :param help: A description of this setting
194 :param parent: Applies only if this is part of a :class:`~confattr.config.DictConfig`
195 '''
196 if type is None:
197 if not allowed_values:
198 raise TypeError("missing required positional argument: 'type'")
199 elif isinstance(allowed_values, dict):
200 type = builtins.type(tuple(allowed_values.values())[0])
201 else:
202 type = builtins.type(allowed_values[0])
203 if not isinstance(type, AbstractFormatter):
204 type = Primitive(type, unit=unit, allowed_values=allowed_values)
205 super().__init__(key,
206 default = None, # type: ignore [arg-type]
207 type = type,
208 help = help,
209 parent = parent,
210 )
212 @typing.overload
213 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self':
214 pass
216 @typing.overload
217 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> T:
218 pass
220 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'T|Self':
221 if instance is None:
222 return self
224 if self.value is None:
225 raise TypeError(f"value for {self.key!r} has not been set")
226 return self.value
229class DictConfig(typing.Generic[T_KEY, T]):
231 '''
232 A container for several settings which belong together.
233 It can be indexed like a normal :class:`dict` but internally the items are stored in :class:`~confattr.config.Config` instances.
235 In contrast to a :class:`~confattr.config.Config` instance it does *not* make a difference whether an instance of this class is accessed as a type or instance attribute.
236 '''
238 def __init__(self,
239 key_prefix: str,
240 default_values: 'dict[T_KEY, T]', *,
241 type: 'CopyableAbstractFormatter[T]|None' = None,
242 ignore_keys: 'Container[T_KEY]' = set(),
243 unit: 'str|None' = None,
244 allowed_values: 'Sequence[T]|dict[str, T]|None' = None,
245 help: 'str|None' = None,
246 ) -> None:
247 '''
248 :param key_prefix: A common prefix which is used by :meth:`~confattr.config.DictConfig.format_key` to generate the :attr:`~confattr.config.Config.key` by which the setting is identified in the config file
249 :param default_values: The content of this container. A :class:`~confattr.config.Config` instance is created for each of these values (except if the key is contained in :paramref:`~confattr.config.DictConfig.ignore_keys`). See :meth:`~confattr.config.DictConfig.format_key`.
250 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`~confattr.config.DictConfig.default_values`. But if you want more control you can implement your own class and pass it to this parameter.
251 :param ignore_keys: All items which have one of these keys are *not* stored in a :class:`~confattr.config.Config` instance, i.e. cannot be set in the config file.
252 :param unit: The unit of all items (only if type is None)
253 :param allowed_values: The possible values these settings can have. Values read from a config file or an environment variable are checked against this. The :paramref:`~confattr.config.DictConfig.default_values` are *not* checked. (Only if type is None.)
254 :param help: A help for all items
256 :raises ValueError: if a key is not unique
257 '''
258 self._values: 'dict[T_KEY, Config[T]]' = {}
259 self._ignored_values: 'dict[T_KEY, T]' = {}
260 self.allowed_values = allowed_values
262 self.key_prefix = key_prefix
263 self.type = type
264 self.unit = unit
265 self.help = help
266 self.ignore_keys = ignore_keys
268 for key, val in default_values.items():
269 self[key] = val
271 def format_key(self, key: T_KEY) -> str:
272 '''
273 Generate a key by which the setting can be identified in the config file based on the dict key by which the value is accessed in the python code.
275 :return: :paramref:`~confattr.config.DictConfig.key_prefix` + dot + :paramref:`~confattr.config.DictConfig.format_key.key`
276 '''
277 key_str = format_primitive_value(key)
278 return '%s.%s' % (self.key_prefix, key_str)
280 def __setitem__(self: 'DictConfig[T_KEY, T]', key: T_KEY, val: T) -> None:
281 if key in self.ignore_keys:
282 self._ignored_values[key] = val
283 return
285 c = self._values.get(key)
286 if c is None:
287 self._values[key] = self.new_config(self.format_key(key), val, unit=self.unit, help=self.help)
288 else:
289 c.value = val
291 def new_config(self: 'DictConfig[T_KEY, T]', key: str, default: T, *, unit: 'str|None', help: 'str|dict[T, str]|None') -> Config[T]:
292 '''
293 Create a new :class:`~confattr.config.Config` instance to be used internally
294 '''
295 return Config(key, default, type=self.type.copy() if self.type else None, unit=unit, help=help, parent=self, allowed_values=self.allowed_values)
297 def __getitem__(self, key: T_KEY) -> T:
298 if key in self.ignore_keys:
299 return self._ignored_values[key]
300 else:
301 return self._values[key].value
303 def get(self, key: T_KEY, default: 'T|None' = None) -> 'T|None':
304 try:
305 return self[key]
306 except KeyError:
307 return default
309 def __repr__(self) -> str:
310 values = {key:val.value for key,val in self._values.items()}
311 values.update({key:val for key,val in self._ignored_values.items()})
312 return '%s(%r, ignore_keys=%r, ...)' % (type(self).__name__, values, self.ignore_keys)
314 def __contains__(self, key: T_KEY) -> bool:
315 if key in self.ignore_keys:
316 return key in self._ignored_values
317 else:
318 return key in self._values
320 def __iter__(self) -> 'Iterator[T_KEY]':
321 yield from self._values
322 yield from self._ignored_values
324 def iter_keys(self) -> 'Iterator[str]':
325 '''
326 Iterate over the keys by which the settings can be identified in the config file
327 '''
328 for cfg in self._values.values():
329 yield cfg.key
332# ========== settings which can have different values for different groups ==========
334class MultiConfig(Config[T]):
336 '''
337 A setting which can have different values for different objects.
339 This class implements the `descriptor protocol <https://docs.python.org/3/reference/datamodel.html#implementing-descriptors>`__ to return one of the values in :attr:`~confattr.config.MultiConfig.values` depending on a ``config_id`` attribute of the owning object if an instance of this class is accessed as an instance attribute.
340 If there is no value for the ``config_id`` in :attr:`~confattr.config.MultiConfig.values` :attr:`~confattr.config.MultiConfig.value` is returned instead.
341 If the owning instance does not have a ``config_id`` attribute an :class:`AttributeError` is raised.
343 In the config file a group can be opened with ``[config-id]``.
344 Then all following ``set`` commands set the value for the specified config id.
345 '''
347 #: A list of all config ids for which a value has been set in any instance of this class (regardless of via code or in a config file and regardless of whether the value has been deleted later on). This list is cleared by :meth:`~confattr.config.MultiConfig.reset`.
348 config_ids: 'list[ConfigId]' = []
350 #: Stores the values for specific objects.
351 values: 'dict[ConfigId, T]'
353 #: Stores the default value which is used if no value for the object is defined in :attr:`~confattr.config.MultiConfig.values`.
354 value: 'T'
356 #: The callable which has been passed to the constructor as :paramref:`~confattr.config.MultiConfig.check_config_id`
357 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None'
359 @classmethod
360 def reset(cls) -> None:
361 '''
362 Clear :attr:`~confattr.config.MultiConfig.config_ids` and clear :attr:`~confattr.config.MultiConfig.values` for all instances in :attr:`Config.instances <confattr.config.Config.instances>`
363 '''
364 cls.config_ids.clear()
365 for cfg in Config.instances.values():
366 if isinstance(cfg, MultiConfig):
367 cfg.values.clear()
369 def __init__(self,
370 key: str,
371 default: T, *,
372 type: 'AbstractFormatter[T]|None' = None,
373 unit: 'str|None' = None,
374 allowed_values: 'Sequence[T]|dict[str, T]|None' = None,
375 help: 'str|dict[T, str]|None' = None,
376 parent: 'MultiDictConfig[typing.Any, T]|None' = None,
377 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None' = None,
378 ) -> None:
379 '''
380 :param key: The name of this setting in the config file
381 :param default: The default value of this setting
382 :param help: A description of this setting
383 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`~confattr.config.MultiConfig.default`. But if :paramref:`~confattr.config.MultiConfig.default` is an empty list the item type cannot be determined automatically so that this argument must be passed explicitly. This also gives the possibility to format a standard type differently e.g. as :class:`~confattr.formatters.Hex`. It is not permissible to reuse the same object for different settings, otherwise :meth:`AbstractFormatter.set_config_key() <confattr.formatters.AbstractFormatter.set_config_key>` will throw an exception.
384 :param unit: The unit of an int or float value (only if type is None)
385 :param allowed_values: The possible values this setting can have. Values read from a config file or an environment variable are checked against this. The :paramref:`~confattr.config.MultiConfig.default` value is *not* checked. (Only if type is None.)
386 :param parent: Applies only if this is part of a :class:`~confattr.config.MultiDictConfig`
387 :param check_config_id: Is called every time a value is set in the config file (except if the config id is :attr:`~confattr.config.Config.default_config_id`—that is always allowed). The callback should raise a :class:`~confattr.configfile.ParseException` if the config id is invalid.
388 '''
389 super().__init__(key, default, type=type, unit=unit, help=help, parent=parent, allowed_values=allowed_values)
390 self.values: 'dict[ConfigId, T]' = {}
391 self.check_config_id = check_config_id
393 # I don't know why this code duplication is necessary,
394 # I have declared the overloads in the parent class already.
395 # But without copy-pasting this code mypy complains
396 # "Signature of __get__ incompatible with supertype Config"
397 @typing.overload
398 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self':
399 pass
401 @typing.overload
402 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> T:
403 pass
405 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'T|Self':
406 if instance is None:
407 return self
409 return self.values.get(instance.config_id, self.value)
411 def __set__(self: 'MultiConfig[T]', instance: typing.Any, value: T) -> None:
412 config_id = instance.config_id
413 self.values[config_id] = value
414 if config_id not in self.config_ids:
415 self.config_ids.append(config_id)
417 def set_value(self: 'MultiConfig[T]', config_id: 'ConfigId|None', value: T) -> None:
418 '''
419 Check :paramref:`~confattr.config.MultiConfig.set_value.config_id` by calling :meth:`~confattr.config.MultiConfig.check_config_id` and
420 set the value for the object(s) identified by :paramref:`~confattr.config.MultiConfig.set_value.config_id`.
422 If you know that :paramref:`~confattr.config.MultiConfig.set_value.config_id` is valid you can also change the items of :attr:`~confattr.config.MultiConfig.values` directly.
423 That is especially useful in test automation with :meth:`pytest.MonkeyPatch.setitem`.
425 If you want to set the default value you can also set :attr:`~confattr.config.MultiConfig.value` directly.
427 :param config_id: Identifies the object(s) for which :paramref:`~confattr.config.MultiConfig.set_value.value` is intended. :obj:`None` is equivalent to :attr:`~confattr.config.MultiConfig.default_config_id`.
428 :param value: The value to be assigned for the object(s) identified by :paramref:`~confattr.config.MultiConfig.set_value.config_id`.
429 '''
430 if config_id is None:
431 config_id = self.default_config_id
432 if self.check_config_id and config_id != self.default_config_id:
433 self.check_config_id(self, config_id)
434 if config_id == self.default_config_id:
435 self.value = value
436 else:
437 self.values[config_id] = value
438 if config_id not in self.config_ids:
439 self.config_ids.append(config_id)
441 def get_value(self, config_id: 'ConfigId|None') -> T:
442 '''
443 :return: The corresponding value from :attr:`~confattr.config.MultiConfig.values` if :paramref:`~confattr.config.MultiConfig.get_value.config_id` is contained or :attr:`~confattr.config.MultiConfig.value` otherwise
444 '''
445 if config_id is None:
446 config_id = self.default_config_id
447 return self.values.get(config_id, self.value)
450class MultiDictConfig(DictConfig[T_KEY, T]):
452 '''
453 A container for several settings which can have different values for different objects.
455 This is essentially a :class:`~confattr.config.DictConfig` using :class:`~confattr.config.MultiConfig` instead of normal :class:`~confattr.config.Config`.
456 However, in order to return different values depending on the ``config_id`` of the owning instance, it implements the `descriptor protocol <https://docs.python.org/3/reference/datamodel.html#implementing-descriptors>`__ to return an :class:`~confattr.config.InstanceSpecificDictMultiConfig` if it is accessed as an instance attribute.
457 '''
459 def __init__(self,
460 key_prefix: str,
461 default_values: 'dict[T_KEY, T]', *,
462 type: 'CopyableAbstractFormatter[T]|None' = None,
463 ignore_keys: 'Container[T_KEY]' = set(),
464 unit: 'str|None' = None,
465 allowed_values: 'Sequence[T]|dict[str, T]|None' = None,
466 help: 'str|None' = None,
467 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None' = None,
468 ) -> None:
469 '''
470 :param key_prefix: A common prefix which is used by :meth:`~confattr.config.MultiDictConfig.format_key` to generate the :attr:`~confattr.config.Config.key` by which the setting is identified in the config file
471 :param default_values: The content of this container. A :class:`~confattr.config.Config` instance is created for each of these values (except if the key is contained in :paramref:`~confattr.config.MultiDictConfig.ignore_keys`). See :meth:`~confattr.config.MultiDictConfig.format_key`.
472 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`~confattr.config.MultiDictConfig.default_values`. But if you want more control you can implement your own class and pass it to this parameter.
473 :param ignore_keys: All items which have one of these keys are *not* stored in a :class:`~confattr.config.Config` instance, i.e. cannot be set in the config file.
474 :param unit: The unit of all items (only if type is None)
475 :param allowed_values: The possible values these settings can have. Values read from a config file or an environment variable are checked against this. The :paramref:`~confattr.config.MultiDictConfig.default_values` are *not* checked. (Only if type is None.)
476 :param help: A help for all items
477 :param check_config_id: Is passed through to :class:`~confattr.config.MultiConfig`
479 :raises ValueError: if a key is not unique
480 '''
481 self.check_config_id = check_config_id
482 super().__init__(
483 key_prefix = key_prefix,
484 default_values = default_values,
485 type = type,
486 ignore_keys = ignore_keys,
487 unit = unit,
488 help = help,
489 allowed_values = allowed_values,
490 )
492 @typing.overload
493 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self':
494 pass
496 @typing.overload
497 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'InstanceSpecificDictMultiConfig[T_KEY, T]':
498 pass
500 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'InstanceSpecificDictMultiConfig[T_KEY, T]|Self':
501 if instance is None:
502 return self
504 return InstanceSpecificDictMultiConfig(self, instance.config_id)
506 def __set__(self: 'MultiDictConfig[T_KEY, T]', instance: typing.Any, value: 'InstanceSpecificDictMultiConfig[T_KEY, T]') -> typing.NoReturn:
507 raise NotImplementedError()
509 def new_config(self: 'MultiDictConfig[T_KEY, T]', key: str, default: T, *, unit: 'str|None', help: 'str|dict[T, str]|None') -> MultiConfig[T]:
510 return MultiConfig(key, default, type=self.type.copy() if self.type else None, unit=unit, help=help, parent=self, allowed_values=self.allowed_values, check_config_id=self.check_config_id)
512class InstanceSpecificDictMultiConfig(typing.Generic[T_KEY, T]):
514 '''
515 An intermediate instance which is returned when accsessing
516 a :class:`~confattr.config.MultiDictConfig` as an instance attribute.
517 Can be indexed like a normal :class:`dict`.
518 '''
520 def __init__(self, mdc: 'MultiDictConfig[T_KEY, T]', config_id: ConfigId) -> None:
521 self.mdc = mdc
522 self.config_id = config_id
524 def __setitem__(self: 'InstanceSpecificDictMultiConfig[T_KEY, T]', key: T_KEY, val: T) -> None:
525 if key in self.mdc.ignore_keys:
526 raise TypeError('cannot set value of ignored key %r' % key)
528 c = self.mdc._values.get(key)
529 if c is None:
530 self.mdc._values[key] = MultiConfig(self.mdc.format_key(key), val, help=self.mdc.help)
531 else:
532 c.__set__(self, val)
534 def __getitem__(self, key: T_KEY) -> T:
535 if key in self.mdc.ignore_keys:
536 return self.mdc._ignored_values[key]
537 else:
538 return self.mdc._values[key].__get__(self)