Coverage for .tox/cov/lib/python3.11/site-packages/confattr/config.py: 100%

189 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-10 20:18 +0200

1#!./runmodule.sh 

2 

3import builtins 

4import enum 

5import typing 

6from collections.abc import Iterable, Iterator, Container, Sequence, Callable 

7 

8if typing.TYPE_CHECKING: 

9 from typing_extensions import Self 

10 

11from .formatters import AbstractFormatter, CopyableAbstractFormatter, Primitive, List, Set, Dict, format_primitive_value 

12 

13 

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) 

16 

17T_KEY = typing.TypeVar('T_KEY') 

18T = typing.TypeVar('T') 

19 

20 

21class Config(typing.Generic[T]): 

22 

23 ''' 

24 Each instance of this class represents a setting which can be changed in a config file. 

25 

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 ''' 

29 

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]]' = {} 

32 

33 default_config_id = ConfigId('general') 

34 

35 #: The value of this setting. 

36 value: 'T' 

37 

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]' 

40 

41 #: A description of this setting or a description for each allowed value. 

42 help: 'str|dict[T, str]|None' 

43 

44 

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` 

62 

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.) 

73 

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.") 

103 

104 type.set_config_key(key) 

105 

106 self._key = key 

107 self.value = default 

108 self.type = type 

109 self.help = help 

110 self.parent = parent 

111 

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 

116 

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 

121 

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 

130 

131 

132 @typing.overload 

133 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self': 

134 pass 

135 

136 @typing.overload 

137 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> T: 

138 pass 

139 

140 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'T|Self': 

141 if instance is None: 

142 return self 

143 

144 return self.value 

145 

146 def __set__(self: 'Config[T]', instance: typing.Any, value: T) -> None: 

147 self.value = value 

148 

149 def __repr__(self) -> str: 

150 return '%s(%s, ...)' % (type(self).__name__, ', '.join(repr(a) for a in (self.key, self.value))) 

151 

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 

162 

163 def wants_to_be_exported(self) -> bool: 

164 return True 

165 

166 def get_value(self, config_id: 'ConfigId|None') -> T: 

167 ''' 

168 :return: :attr:`~confattr.config.Config.value` 

169 

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 

173 

174 

175class DictConfig(typing.Generic[T_KEY, T]): 

176 

177 ''' 

178 A container for several settings which belong together. 

179 It can be indexed like a normal :class:`dict` but internally the items are stored in :class:`~confattr.config.Config` instances. 

180 

181 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. 

182 ''' 

183 

184 def __init__(self, 

185 key_prefix: str, 

186 default_values: 'dict[T_KEY, T]', *, 

187 type: 'CopyableAbstractFormatter[T]|None' = None, 

188 ignore_keys: 'Container[T_KEY]' = set(), 

189 unit: 'str|None' = None, 

190 allowed_values: 'Sequence[T]|dict[str, T]|None' = None, 

191 help: 'str|None' = None, 

192 ) -> None: 

193 ''' 

194 :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 

195 :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`. 

196 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`default_values`. But if you want more control you can implement your own class and pass it to this parameter. 

197 :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. 

198 :param unit: The unit of all items (only if type is None) 

199 :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.) 

200 :param help: A help for all items 

201 

202 :raises ValueError: if a key is not unique 

203 ''' 

204 self._values: 'dict[T_KEY, Config[T]]' = {} 

205 self._ignored_values: 'dict[T_KEY, T]' = {} 

206 self.allowed_values = allowed_values 

207 

208 self.key_prefix = key_prefix 

209 self.type = type 

210 self.unit = unit 

211 self.help = help 

212 self.ignore_keys = ignore_keys 

213 

214 for key, val in default_values.items(): 

215 self[key] = val 

216 

217 def format_key(self, key: T_KEY) -> str: 

218 ''' 

219 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. 

220 

221 :return: :paramref:`~confattr.config.DictConfig.key_prefix` + dot + :paramref:`~confattr.config.DictConfig.format_key.key` 

222 ''' 

223 key_str = format_primitive_value(key) 

224 return '%s.%s' % (self.key_prefix, key_str) 

225 

226 def __setitem__(self: 'DictConfig[T_KEY, T]', key: T_KEY, val: T) -> None: 

227 if key in self.ignore_keys: 

228 self._ignored_values[key] = val 

229 return 

230 

231 c = self._values.get(key) 

232 if c is None: 

233 self._values[key] = self.new_config(self.format_key(key), val, unit=self.unit, help=self.help) 

234 else: 

235 c.value = val 

236 

237 def new_config(self: 'DictConfig[T_KEY, T]', key: str, default: T, *, unit: 'str|None', help: 'str|dict[T, str]|None') -> Config[T]: 

238 ''' 

239 Create a new :class:`~confattr.config.Config` instance to be used internally 

240 ''' 

241 return Config(key, default, type=self.type.copy() if self.type else None, unit=unit, help=help, parent=self, allowed_values=self.allowed_values) 

242 

243 def __getitem__(self, key: T_KEY) -> T: 

244 if key in self.ignore_keys: 

245 return self._ignored_values[key] 

246 else: 

247 return self._values[key].value 

248 

249 def get(self, key: T_KEY, default: 'T|None' = None) -> 'T|None': 

250 try: 

251 return self[key] 

252 except KeyError: 

253 return default 

254 

255 def __repr__(self) -> str: 

256 values = {key:val.value for key,val in self._values.items()} 

257 values.update({key:val for key,val in self._ignored_values.items()}) 

258 return '%s(%r, ignore_keys=%r, ...)' % (type(self).__name__, values, self.ignore_keys) 

259 

260 def __contains__(self, key: T_KEY) -> bool: 

261 if key in self.ignore_keys: 

262 return key in self._ignored_values 

263 else: 

264 return key in self._values 

265 

266 def __iter__(self) -> 'Iterator[T_KEY]': 

267 yield from self._values 

268 yield from self._ignored_values 

269 

270 def iter_keys(self) -> 'Iterator[str]': 

271 ''' 

272 Iterate over the keys by which the settings can be identified in the config file 

273 ''' 

274 for cfg in self._values.values(): 

275 yield cfg.key 

276 

277 

278# ========== settings which can have different values for different groups ========== 

279 

280class MultiConfig(Config[T]): 

281 

282 ''' 

283 A setting which can have different values for different objects. 

284 

285 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. 

286 If there is no value for the ``config_id`` in :attr:`~confattr.config.MultiConfig.values` :attr:`~confattr.config.MultiConfig.value` is returned instead. 

287 If the owning instance does not have a ``config_id`` attribute an :class:`AttributeError` is raised. 

288 

289 In the config file a group can be opened with ``[config-id]``. 

290 Then all following ``set`` commands set the value for the specified config id. 

291 ''' 

292 

293 #: 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`. 

294 config_ids: 'list[ConfigId]' = [] 

295 

296 #: Stores the values for specific objects. 

297 values: 'dict[ConfigId, T]' 

298 

299 #: Stores the default value which is used if no value for the object is defined in :attr:`~confattr.config.MultiConfig.values`. 

300 value: 'T' 

301 

302 #: The callable which has been passed to the constructor as :paramref:`~confattr.config.MultiConfig.check_config_id` 

303 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None' 

304 

305 @classmethod 

306 def reset(cls) -> None: 

307 ''' 

308 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>` 

309 ''' 

310 cls.config_ids.clear() 

311 for cfg in Config.instances.values(): 

312 if isinstance(cfg, MultiConfig): 

313 cfg.values.clear() 

314 

315 def __init__(self, 

316 key: str, 

317 default: T, *, 

318 type: 'AbstractFormatter[T]|None' = None, 

319 unit: 'str|None' = None, 

320 allowed_values: 'Sequence[T]|dict[str, T]|None' = None, 

321 help: 'str|dict[T, str]|None' = None, 

322 parent: 'MultiDictConfig[typing.Any, T]|None' = None, 

323 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None' = None, 

324 ) -> None: 

325 ''' 

326 :param key: The name of this setting in the config file 

327 :param default: The default value of this setting 

328 :param help: A description of this setting 

329 :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. 

330 :param unit: The unit of an int or float value (only if type is None) 

331 :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.) 

332 :param parent: Applies only if this is part of a :class:`~confattr.config.MultiDictConfig` 

333 :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. 

334 ''' 

335 super().__init__(key, default, type=type, unit=unit, help=help, parent=parent, allowed_values=allowed_values) 

336 self.values: 'dict[ConfigId, T]' = {} 

337 self.check_config_id = check_config_id 

338 

339 # I don't know why this code duplication is necessary, 

340 # I have declared the overloads in the parent class already. 

341 # But without copy-pasting this code mypy complains 

342 # "Signature of __get__ incompatible with supertype Config" 

343 @typing.overload 

344 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self': 

345 pass 

346 

347 @typing.overload 

348 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> T: 

349 pass 

350 

351 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'T|Self': 

352 if instance is None: 

353 return self 

354 

355 return self.values.get(instance.config_id, self.value) 

356 

357 def __set__(self: 'MultiConfig[T]', instance: typing.Any, value: T) -> None: 

358 config_id = instance.config_id 

359 self.values[config_id] = value 

360 if config_id not in self.config_ids: 

361 self.config_ids.append(config_id) 

362 

363 def set_value(self: 'MultiConfig[T]', config_id: 'ConfigId|None', value: T) -> None: 

364 ''' 

365 Check :paramref:`~confattr.config.MultiConfig.set_value.config_id` by calling :meth:`~confattr.config.MultiConfig.check_config_id` and 

366 set the value for the object(s) identified by :paramref:`~confattr.config.MultiConfig.set_value.config_id`. 

367 

368 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. 

369 That is especially useful in test automation with :meth:`pytest.MonkeyPatch.setitem`. 

370 

371 If you want to set the default value you can also set :attr:`~confattr.config.MultiConfig.value` directly. 

372 

373 :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`. 

374 :param value: The value to be assigned for the object(s) identified by :paramref:`~confattr.config.MultiConfig.set_value.config_id`. 

375 ''' 

376 if config_id is None: 

377 config_id = self.default_config_id 

378 if self.check_config_id and config_id != self.default_config_id: 

379 self.check_config_id(self, config_id) 

380 if config_id == self.default_config_id: 

381 self.value = value 

382 else: 

383 self.values[config_id] = value 

384 if config_id not in self.config_ids: 

385 self.config_ids.append(config_id) 

386 

387 def get_value(self, config_id: 'ConfigId|None') -> T: 

388 ''' 

389 :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 

390 ''' 

391 if config_id is None: 

392 config_id = self.default_config_id 

393 return self.values.get(config_id, self.value) 

394 

395 

396class MultiDictConfig(DictConfig[T_KEY, T]): 

397 

398 ''' 

399 A container for several settings which can have different values for different objects. 

400 

401 This is essentially a :class:`~confattr.config.DictConfig` using :class:`~confattr.config.MultiConfig` instead of normal :class:`~confattr.config.Config`. 

402 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. 

403 ''' 

404 

405 def __init__(self, 

406 key_prefix: str, 

407 default_values: 'dict[T_KEY, T]', *, 

408 type: 'CopyableAbstractFormatter[T]|None' = None, 

409 ignore_keys: 'Container[T_KEY]' = set(), 

410 unit: 'str|None' = None, 

411 allowed_values: 'Sequence[T]|dict[str, T]|None' = None, 

412 help: 'str|None' = None, 

413 check_config_id: 'Callable[[MultiConfig[T], ConfigId], None]|None' = None, 

414 ) -> None: 

415 ''' 

416 :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 

417 :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`. 

418 :param type: How to parse, format and complete a value. Usually this is determined automatically based on :paramref:`default_values`. But if you want more control you can implement your own class and pass it to this parameter. 

419 :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. 

420 :param unit: The unit of all items (only if type is None) 

421 :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.) 

422 :param help: A help for all items 

423 :param check_config_id: Is passed through to :class:`~confattr.config.MultiConfig` 

424 

425 :raises ValueError: if a key is not unique 

426 ''' 

427 self.check_config_id = check_config_id 

428 super().__init__( 

429 key_prefix = key_prefix, 

430 default_values = default_values, 

431 type = type, 

432 ignore_keys = ignore_keys, 

433 unit = unit, 

434 help = help, 

435 allowed_values = allowed_values, 

436 ) 

437 

438 @typing.overload 

439 def __get__(self, instance: None, owner: typing.Any = None) -> 'Self': 

440 pass 

441 

442 @typing.overload 

443 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'InstanceSpecificDictMultiConfig[T_KEY, T]': 

444 pass 

445 

446 def __get__(self, instance: typing.Any, owner: typing.Any = None) -> 'InstanceSpecificDictMultiConfig[T_KEY, T]|Self': 

447 if instance is None: 

448 return self 

449 

450 return InstanceSpecificDictMultiConfig(self, instance.config_id) 

451 

452 def __set__(self: 'MultiDictConfig[T_KEY, T]', instance: typing.Any, value: 'InstanceSpecificDictMultiConfig[T_KEY, T]') -> typing.NoReturn: 

453 raise NotImplementedError() 

454 

455 def new_config(self: 'MultiDictConfig[T_KEY, T]', key: str, default: T, *, unit: 'str|None', help: 'str|dict[T, str]|None') -> MultiConfig[T]: 

456 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) 

457 

458class InstanceSpecificDictMultiConfig(typing.Generic[T_KEY, T]): 

459 

460 ''' 

461 An intermediate instance which is returned when accsessing 

462 a :class:`~confattr.config.MultiDictConfig` as an instance attribute. 

463 Can be indexed like a normal :class:`dict`. 

464 ''' 

465 

466 def __init__(self, mdc: 'MultiDictConfig[T_KEY, T]', config_id: ConfigId) -> None: 

467 self.mdc = mdc 

468 self.config_id = config_id 

469 

470 def __setitem__(self: 'InstanceSpecificDictMultiConfig[T_KEY, T]', key: T_KEY, val: T) -> None: 

471 if key in self.mdc.ignore_keys: 

472 raise TypeError('cannot set value of ignored key %r' % key) 

473 

474 c = self.mdc._values.get(key) 

475 if c is None: 

476 self.mdc._values[key] = MultiConfig(self.mdc.format_key(key), val, help=self.mdc.help) 

477 else: 

478 c.__set__(self, val) 

479 

480 def __getitem__(self, key: T_KEY) -> T: 

481 if key in self.mdc.ignore_keys: 

482 return self.mdc._ignored_values[key] 

483 else: 

484 return self.mdc._values[key].__get__(self)