dictconfig.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. import copy
  2. from enum import Enum
  3. from typing import (
  4. Any,
  5. Dict,
  6. ItemsView,
  7. Iterable,
  8. Iterator,
  9. KeysView,
  10. List,
  11. MutableMapping,
  12. Optional,
  13. Sequence,
  14. Tuple,
  15. Type,
  16. Union,
  17. )
  18. from ._utils import (
  19. _DEFAULT_MARKER_,
  20. ValueKind,
  21. _get_value,
  22. _is_interpolation,
  23. _is_missing_literal,
  24. _is_missing_value,
  25. _is_none,
  26. _resolve_optional,
  27. _valid_dict_key_annotation_type,
  28. format_and_raise,
  29. get_structured_config_data,
  30. get_structured_config_init_field_names,
  31. get_type_of,
  32. get_value_kind,
  33. is_container_annotation,
  34. is_dict,
  35. is_primitive_dict,
  36. is_structured_config,
  37. is_structured_config_frozen,
  38. type_str,
  39. )
  40. from .base import Box, Container, ContainerMetadata, DictKeyType, Node
  41. from .basecontainer import BaseContainer
  42. from .errors import (
  43. ConfigAttributeError,
  44. ConfigKeyError,
  45. ConfigTypeError,
  46. InterpolationResolutionError,
  47. KeyValidationError,
  48. MissingMandatoryValue,
  49. OmegaConfBaseException,
  50. ReadonlyConfigError,
  51. ValidationError,
  52. )
  53. from .nodes import EnumNode, ValueNode
  54. class DictConfig(BaseContainer, MutableMapping[Any, Any]):
  55. _metadata: ContainerMetadata
  56. _content: Union[Dict[DictKeyType, Node], None, str]
  57. def __init__(
  58. self,
  59. content: Union[Dict[DictKeyType, Any], "DictConfig", Any],
  60. key: Any = None,
  61. parent: Optional[Box] = None,
  62. ref_type: Union[Any, Type[Any]] = Any,
  63. key_type: Union[Any, Type[Any]] = Any,
  64. element_type: Union[Any, Type[Any]] = Any,
  65. is_optional: bool = True,
  66. flags: Optional[Dict[str, bool]] = None,
  67. ) -> None:
  68. try:
  69. if isinstance(content, DictConfig):
  70. if flags is None:
  71. flags = content._metadata.flags
  72. super().__init__(
  73. parent=parent,
  74. metadata=ContainerMetadata(
  75. key=key,
  76. optional=is_optional,
  77. ref_type=ref_type,
  78. object_type=dict,
  79. key_type=key_type,
  80. element_type=element_type,
  81. flags=flags,
  82. ),
  83. )
  84. if not _valid_dict_key_annotation_type(key_type):
  85. raise KeyValidationError(f"Unsupported key type {key_type}")
  86. if is_structured_config(content) or is_structured_config(ref_type):
  87. self._set_value(content, flags=flags)
  88. if is_structured_config_frozen(content) or is_structured_config_frozen(
  89. ref_type
  90. ):
  91. self._set_flag("readonly", True)
  92. else:
  93. if isinstance(content, DictConfig):
  94. metadata = copy.deepcopy(content._metadata)
  95. metadata.key = key
  96. metadata.ref_type = ref_type
  97. metadata.optional = is_optional
  98. metadata.element_type = element_type
  99. metadata.key_type = key_type
  100. self.__dict__["_metadata"] = metadata
  101. self._set_value(content, flags=flags)
  102. except Exception as ex:
  103. format_and_raise(node=None, key=key, value=None, cause=ex, msg=str(ex))
  104. def __deepcopy__(self, memo: Dict[int, Any]) -> "DictConfig":
  105. res = DictConfig(None)
  106. res.__dict__["_metadata"] = copy.deepcopy(self.__dict__["_metadata"], memo=memo)
  107. res.__dict__["_flags_cache"] = copy.deepcopy(
  108. self.__dict__["_flags_cache"], memo=memo
  109. )
  110. src_content = self.__dict__["_content"]
  111. if isinstance(src_content, dict):
  112. content_copy = {}
  113. for k, v in src_content.items():
  114. old_parent = v.__dict__["_parent"]
  115. try:
  116. v.__dict__["_parent"] = None
  117. vc = copy.deepcopy(v, memo=memo)
  118. vc.__dict__["_parent"] = res
  119. content_copy[k] = vc
  120. finally:
  121. v.__dict__["_parent"] = old_parent
  122. else:
  123. # None and strings can be assigned as is
  124. content_copy = src_content
  125. res.__dict__["_content"] = content_copy
  126. # parent is retained, but not copied
  127. res.__dict__["_parent"] = self.__dict__["_parent"]
  128. return res
  129. def copy(self) -> "DictConfig":
  130. return copy.copy(self)
  131. def _is_typed(self) -> bool:
  132. return self._metadata.object_type not in (Any, None) and not is_dict(
  133. self._metadata.object_type
  134. )
  135. def _validate_get(self, key: Any, value: Any = None) -> None:
  136. is_typed = self._is_typed()
  137. is_struct = self._get_flag("struct") is True
  138. if key not in self.__dict__["_content"]:
  139. if is_typed:
  140. # do not raise an exception if struct is explicitly set to False
  141. if self._get_node_flag("struct") is False:
  142. return
  143. if is_typed or is_struct:
  144. if is_typed:
  145. assert self._metadata.object_type not in (dict, None)
  146. msg = f"Key '{key}' not in '{self._metadata.object_type.__name__}'"
  147. else:
  148. msg = f"Key '{key}' is not in struct"
  149. self._format_and_raise(
  150. key=key, value=value, cause=ConfigAttributeError(msg)
  151. )
  152. def _validate_set(self, key: Any, value: Any) -> None:
  153. from omegaconf import OmegaConf
  154. vk = get_value_kind(value)
  155. if vk == ValueKind.INTERPOLATION:
  156. return
  157. if _is_none(value):
  158. self._validate_non_optional(key, value)
  159. return
  160. if vk == ValueKind.MANDATORY_MISSING or value is None:
  161. return
  162. target = self._get_node(key) if key is not None else self
  163. target_has_ref_type = isinstance(
  164. target, DictConfig
  165. ) and target._metadata.ref_type not in (Any, dict)
  166. is_valid_target = target is None or not target_has_ref_type
  167. if is_valid_target:
  168. return
  169. assert isinstance(target, Node)
  170. target_type = target._metadata.ref_type
  171. value_type = OmegaConf.get_type(value)
  172. if is_dict(value_type) and is_dict(target_type):
  173. return
  174. if is_container_annotation(target_type) and not is_container_annotation(
  175. value_type
  176. ):
  177. raise ValidationError(
  178. f"Cannot assign {type_str(value_type)} to {type_str(target_type)}"
  179. )
  180. if target_type is not None and value_type is not None:
  181. origin = getattr(target_type, "__origin__", target_type)
  182. if not issubclass(value_type, origin):
  183. self._raise_invalid_value(value, value_type, target_type)
  184. def _validate_merge(self, value: Any) -> None:
  185. from omegaconf import OmegaConf
  186. dest = self
  187. src = value
  188. self._validate_non_optional(None, src)
  189. dest_obj_type = OmegaConf.get_type(dest)
  190. src_obj_type = OmegaConf.get_type(src)
  191. if dest._is_missing() and src._metadata.object_type not in (dict, None):
  192. self._validate_set(key=None, value=_get_value(src))
  193. if src._is_missing():
  194. return
  195. validation_error = (
  196. dest_obj_type is not None
  197. and src_obj_type is not None
  198. and is_structured_config(dest_obj_type)
  199. and not src._is_none()
  200. and not is_dict(src_obj_type)
  201. and not issubclass(src_obj_type, dest_obj_type)
  202. )
  203. if validation_error:
  204. msg = (
  205. f"Merge error: {type_str(src_obj_type)} is not a "
  206. f"subclass of {type_str(dest_obj_type)}. value: {src}"
  207. )
  208. raise ValidationError(msg)
  209. def _validate_non_optional(self, key: Optional[DictKeyType], value: Any) -> None:
  210. if _is_none(value, resolve=True, throw_on_resolution_failure=False):
  211. if key is not None:
  212. child = self._get_node(key)
  213. if child is not None:
  214. assert isinstance(child, Node)
  215. field_is_optional = child._is_optional()
  216. else:
  217. field_is_optional, _ = _resolve_optional(
  218. self._metadata.element_type
  219. )
  220. else:
  221. field_is_optional = self._is_optional()
  222. if not field_is_optional:
  223. self._format_and_raise(
  224. key=key,
  225. value=value,
  226. cause=ValidationError("field '$FULL_KEY' is not Optional"),
  227. )
  228. def _raise_invalid_value(
  229. self, value: Any, value_type: Any, target_type: Any
  230. ) -> None:
  231. assert value_type is not None
  232. assert target_type is not None
  233. msg = (
  234. f"Invalid type assigned: {type_str(value_type)} is not a "
  235. f"subclass of {type_str(target_type)}. value: {value}"
  236. )
  237. raise ValidationError(msg)
  238. def _validate_and_normalize_key(self, key: Any) -> DictKeyType:
  239. return self._s_validate_and_normalize_key(self._metadata.key_type, key)
  240. def _s_validate_and_normalize_key(self, key_type: Any, key: Any) -> DictKeyType:
  241. if key_type is Any:
  242. for t in DictKeyType.__args__: # type: ignore
  243. if isinstance(key, t):
  244. return key # type: ignore
  245. raise KeyValidationError("Incompatible key type '$KEY_TYPE'")
  246. elif key_type is bool and key in [0, 1]:
  247. # Python treats True as 1 and False as 0 when used as dict keys
  248. # assert hash(0) == hash(False)
  249. # assert hash(1) == hash(True)
  250. return bool(key)
  251. elif key_type in (str, bytes, int, float, bool): # primitive type
  252. if not isinstance(key, key_type):
  253. raise KeyValidationError(
  254. f"Key $KEY ($KEY_TYPE) is incompatible with ({key_type.__name__})"
  255. )
  256. return key # type: ignore
  257. elif issubclass(key_type, Enum):
  258. try:
  259. return EnumNode.validate_and_convert_to_enum(key_type, key)
  260. except ValidationError:
  261. valid = ", ".join([x for x in key_type.__members__.keys()])
  262. raise KeyValidationError(
  263. f"Key '$KEY' is incompatible with the enum type '{key_type.__name__}', valid: [{valid}]"
  264. )
  265. else:
  266. assert False, f"Unsupported key type {key_type}"
  267. def __setitem__(self, key: DictKeyType, value: Any) -> None:
  268. try:
  269. self.__set_impl(key=key, value=value)
  270. except AttributeError as e:
  271. self._format_and_raise(
  272. key=key, value=value, type_override=ConfigKeyError, cause=e
  273. )
  274. except Exception as e:
  275. self._format_and_raise(key=key, value=value, cause=e)
  276. def __set_impl(self, key: DictKeyType, value: Any) -> None:
  277. key = self._validate_and_normalize_key(key)
  278. self._set_item_impl(key, value)
  279. # hide content while inspecting in debugger
  280. def __dir__(self) -> Iterable[str]:
  281. if self._is_missing() or self._is_none():
  282. return []
  283. return self.__dict__["_content"].keys() # type: ignore
  284. def __setattr__(self, key: str, value: Any) -> None:
  285. """
  286. Allow assigning attributes to DictConfig
  287. :param key:
  288. :param value:
  289. :return:
  290. """
  291. try:
  292. self.__set_impl(key, value)
  293. except Exception as e:
  294. if isinstance(e, OmegaConfBaseException) and e._initialized:
  295. raise e
  296. self._format_and_raise(key=key, value=value, cause=e)
  297. assert False
  298. def __getattr__(self, key: str) -> Any:
  299. """
  300. Allow accessing dictionary values as attributes
  301. :param key:
  302. :return:
  303. """
  304. if key == "__name__":
  305. raise AttributeError()
  306. try:
  307. return self._get_impl(
  308. key=key, default_value=_DEFAULT_MARKER_, validate_key=False
  309. )
  310. except ConfigKeyError as e:
  311. self._format_and_raise(
  312. key=key, value=None, cause=e, type_override=ConfigAttributeError
  313. )
  314. except Exception as e:
  315. self._format_and_raise(key=key, value=None, cause=e)
  316. def __getitem__(self, key: DictKeyType) -> Any:
  317. """
  318. Allow map style access
  319. :param key:
  320. :return:
  321. """
  322. try:
  323. return self._get_impl(key=key, default_value=_DEFAULT_MARKER_)
  324. except AttributeError as e:
  325. self._format_and_raise(
  326. key=key, value=None, cause=e, type_override=ConfigKeyError
  327. )
  328. except Exception as e:
  329. self._format_and_raise(key=key, value=None, cause=e)
  330. def __delattr__(self, key: str) -> None:
  331. """
  332. Allow deleting dictionary values as attributes
  333. :param key:
  334. :return:
  335. """
  336. if self._get_flag("readonly"):
  337. self._format_and_raise(
  338. key=key,
  339. value=None,
  340. cause=ReadonlyConfigError(
  341. "DictConfig in read-only mode does not support deletion"
  342. ),
  343. )
  344. try:
  345. del self.__dict__["_content"][key]
  346. except KeyError:
  347. msg = "Attribute not found: '$KEY'"
  348. self._format_and_raise(key=key, value=None, cause=ConfigAttributeError(msg))
  349. def __delitem__(self, key: DictKeyType) -> None:
  350. key = self._validate_and_normalize_key(key)
  351. if self._get_flag("readonly"):
  352. self._format_and_raise(
  353. key=key,
  354. value=None,
  355. cause=ReadonlyConfigError(
  356. "DictConfig in read-only mode does not support deletion"
  357. ),
  358. )
  359. if self._get_flag("struct"):
  360. self._format_and_raise(
  361. key=key,
  362. value=None,
  363. cause=ConfigTypeError(
  364. "DictConfig in struct mode does not support deletion"
  365. ),
  366. )
  367. if self._is_typed() and self._get_node_flag("struct") is not False:
  368. self._format_and_raise(
  369. key=key,
  370. value=None,
  371. cause=ConfigTypeError(
  372. f"{type_str(self._metadata.object_type)} (DictConfig) does not support deletion"
  373. ),
  374. )
  375. try:
  376. del self.__dict__["_content"][key]
  377. except KeyError:
  378. msg = "Key not found: '$KEY'"
  379. self._format_and_raise(key=key, value=None, cause=ConfigKeyError(msg))
  380. def get(self, key: DictKeyType, default_value: Any = None) -> Any:
  381. """Return the value for `key` if `key` is in the dictionary, else
  382. `default_value` (defaulting to `None`)."""
  383. try:
  384. return self._get_impl(key=key, default_value=default_value)
  385. except KeyValidationError as e:
  386. self._format_and_raise(key=key, value=None, cause=e)
  387. def _get_impl(
  388. self, key: DictKeyType, default_value: Any, validate_key: bool = True
  389. ) -> Any:
  390. try:
  391. node = self._get_child(
  392. key=key, throw_on_missing_key=True, validate_key=validate_key
  393. )
  394. except (ConfigAttributeError, ConfigKeyError):
  395. if default_value is not _DEFAULT_MARKER_:
  396. return default_value
  397. else:
  398. raise
  399. assert isinstance(node, Node)
  400. return self._resolve_with_default(
  401. key=key, value=node, default_value=default_value
  402. )
  403. def _get_node(
  404. self,
  405. key: DictKeyType,
  406. validate_access: bool = True,
  407. validate_key: bool = True,
  408. throw_on_missing_value: bool = False,
  409. throw_on_missing_key: bool = False,
  410. ) -> Optional[Node]:
  411. try:
  412. key = self._validate_and_normalize_key(key)
  413. except KeyValidationError:
  414. if validate_access and validate_key:
  415. raise
  416. else:
  417. if throw_on_missing_key:
  418. raise ConfigAttributeError
  419. else:
  420. return None
  421. if validate_access:
  422. self._validate_get(key)
  423. value: Optional[Node] = self.__dict__["_content"].get(key)
  424. if value is None:
  425. if throw_on_missing_key:
  426. raise ConfigKeyError(f"Missing key {key!s}")
  427. elif throw_on_missing_value and value._is_missing():
  428. raise MissingMandatoryValue("Missing mandatory value: $KEY")
  429. return value
  430. def pop(self, key: DictKeyType, default: Any = _DEFAULT_MARKER_) -> Any:
  431. try:
  432. if self._get_flag("readonly"):
  433. raise ReadonlyConfigError("Cannot pop from read-only node")
  434. if self._get_flag("struct"):
  435. raise ConfigTypeError("DictConfig in struct mode does not support pop")
  436. if self._is_typed() and self._get_node_flag("struct") is not False:
  437. raise ConfigTypeError(
  438. f"{type_str(self._metadata.object_type)} (DictConfig) does not support pop"
  439. )
  440. key = self._validate_and_normalize_key(key)
  441. node = self._get_child(key=key, validate_access=False)
  442. if node is not None:
  443. assert isinstance(node, Node)
  444. value = self._resolve_with_default(
  445. key=key, value=node, default_value=default
  446. )
  447. del self[key]
  448. return value
  449. else:
  450. if default is not _DEFAULT_MARKER_:
  451. return default
  452. else:
  453. full = self._get_full_key(key=key)
  454. if full != key:
  455. raise ConfigKeyError(
  456. f"Key not found: '{key!s}' (path: '{full}')"
  457. )
  458. else:
  459. raise ConfigKeyError(f"Key not found: '{key!s}'")
  460. except Exception as e:
  461. self._format_and_raise(key=key, value=None, cause=e)
  462. def keys(self) -> KeysView[DictKeyType]:
  463. if self._is_missing() or self._is_interpolation() or self._is_none():
  464. return {}.keys()
  465. ret = self.__dict__["_content"].keys()
  466. assert isinstance(ret, KeysView)
  467. return ret
  468. def __contains__(self, key: object) -> bool:
  469. """
  470. A key is contained in a DictConfig if there is an associated value and
  471. it is not a mandatory missing value ('???').
  472. :param key:
  473. :return:
  474. """
  475. try:
  476. key = self._validate_and_normalize_key(key)
  477. except KeyValidationError:
  478. return False
  479. try:
  480. node = self._get_child(key)
  481. assert node is None or isinstance(node, Node)
  482. except (KeyError, AttributeError):
  483. node = None
  484. if node is None:
  485. return False
  486. else:
  487. try:
  488. self._resolve_with_default(key=key, value=node)
  489. return True
  490. except InterpolationResolutionError:
  491. # Interpolations that fail count as existing.
  492. return True
  493. except MissingMandatoryValue:
  494. # Missing values count as *not* existing.
  495. return False
  496. def __iter__(self) -> Iterator[DictKeyType]:
  497. return iter(self.keys())
  498. def items(self) -> ItemsView[DictKeyType, Any]:
  499. return dict(self.items_ex(resolve=True, keys=None)).items()
  500. def setdefault(self, key: DictKeyType, default: Any = None) -> Any:
  501. if key in self:
  502. ret = self.__getitem__(key)
  503. else:
  504. ret = default
  505. self.__setitem__(key, default)
  506. return ret
  507. def items_ex(
  508. self, resolve: bool = True, keys: Optional[Sequence[DictKeyType]] = None
  509. ) -> List[Tuple[DictKeyType, Any]]:
  510. items: List[Tuple[DictKeyType, Any]] = []
  511. if self._is_none():
  512. self._format_and_raise(
  513. key=None,
  514. value=None,
  515. cause=TypeError("Cannot iterate a DictConfig object representing None"),
  516. )
  517. if self._is_missing():
  518. raise MissingMandatoryValue("Cannot iterate a missing DictConfig")
  519. for key in self.keys():
  520. if resolve:
  521. value = self[key]
  522. else:
  523. value = self.__dict__["_content"][key]
  524. if isinstance(value, ValueNode):
  525. value = value._value()
  526. if keys is None or key in keys:
  527. items.append((key, value))
  528. return items
  529. def __eq__(self, other: Any) -> bool:
  530. if other is None:
  531. return self.__dict__["_content"] is None
  532. if is_primitive_dict(other) or is_structured_config(other):
  533. other = DictConfig(other, flags={"allow_objects": True})
  534. return DictConfig._dict_conf_eq(self, other)
  535. if isinstance(other, DictConfig):
  536. return DictConfig._dict_conf_eq(self, other)
  537. if self._is_missing():
  538. return _is_missing_literal(other)
  539. return NotImplemented
  540. def __ne__(self, other: Any) -> bool:
  541. x = self.__eq__(other)
  542. if x is not NotImplemented:
  543. return not x
  544. return NotImplemented
  545. def __hash__(self) -> int:
  546. return hash(str(self))
  547. def _promote(self, type_or_prototype: Optional[Type[Any]]) -> None:
  548. """
  549. Retypes a node.
  550. This should only be used in rare circumstances, where you want to dynamically change
  551. the runtime structured-type of a DictConfig.
  552. It will change the type and add the additional fields based on the input class or object
  553. """
  554. if type_or_prototype is None:
  555. return
  556. if not is_structured_config(type_or_prototype):
  557. raise ValueError(f"Expected structured config class: {type_or_prototype}")
  558. from omegaconf import OmegaConf
  559. proto: DictConfig = OmegaConf.structured(type_or_prototype)
  560. object_type = proto._metadata.object_type
  561. # remove the type to prevent assignment validation from rejecting the promotion.
  562. proto._metadata.object_type = None
  563. self.merge_with(proto)
  564. # restore the type.
  565. self._metadata.object_type = object_type
  566. def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
  567. try:
  568. previous_content = self.__dict__["_content"]
  569. self._set_value_impl(value, flags)
  570. except Exception as e:
  571. self.__dict__["_content"] = previous_content
  572. raise e
  573. def _set_value_impl(
  574. self, value: Any, flags: Optional[Dict[str, bool]] = None
  575. ) -> None:
  576. from omegaconf import MISSING, flag_override
  577. if flags is None:
  578. flags = {}
  579. assert not isinstance(value, ValueNode)
  580. self._validate_set(key=None, value=value)
  581. if _is_none(value, resolve=True):
  582. self.__dict__["_content"] = None
  583. self._metadata.object_type = None
  584. elif _is_interpolation(value, strict_interpolation_validation=True):
  585. self.__dict__["_content"] = value
  586. self._metadata.object_type = None
  587. elif _is_missing_value(value):
  588. self.__dict__["_content"] = MISSING
  589. self._metadata.object_type = None
  590. else:
  591. self.__dict__["_content"] = {}
  592. if is_structured_config(value):
  593. self._metadata.object_type = None
  594. ao = self._get_flag("allow_objects")
  595. data = get_structured_config_data(value, allow_objects=ao)
  596. with flag_override(self, ["struct", "readonly"], False):
  597. for k, v in data.items():
  598. self.__setitem__(k, v)
  599. self._metadata.object_type = get_type_of(value)
  600. elif isinstance(value, DictConfig):
  601. self._metadata.flags = copy.deepcopy(flags)
  602. with flag_override(self, ["struct", "readonly"], False):
  603. for k, v in value.__dict__["_content"].items():
  604. self.__setitem__(k, v)
  605. self._metadata.object_type = value._metadata.object_type
  606. elif isinstance(value, dict):
  607. with flag_override(self, ["struct", "readonly"], False):
  608. for k, v in value.items():
  609. self.__setitem__(k, v)
  610. self._metadata.object_type = dict
  611. else: # pragma: no cover
  612. msg = f"Unsupported value type: {value}"
  613. raise ValidationError(msg)
  614. @staticmethod
  615. def _dict_conf_eq(d1: "DictConfig", d2: "DictConfig") -> bool:
  616. d1_none = d1.__dict__["_content"] is None
  617. d2_none = d2.__dict__["_content"] is None
  618. if d1_none and d2_none:
  619. return True
  620. if d1_none != d2_none:
  621. return False
  622. assert isinstance(d1, DictConfig)
  623. assert isinstance(d2, DictConfig)
  624. if len(d1) != len(d2):
  625. return False
  626. if d1._is_missing() or d2._is_missing():
  627. return d1._is_missing() is d2._is_missing()
  628. for k, v in d1.items_ex(resolve=False):
  629. if k not in d2.__dict__["_content"]:
  630. return False
  631. if not BaseContainer._item_eq(d1, k, d2, k):
  632. return False
  633. return True
  634. def _to_object(self) -> Any:
  635. """
  636. Instantiate an instance of `self._metadata.object_type`.
  637. This requires `self` to be a structured config.
  638. Nested subconfigs are converted by calling `OmegaConf.to_object`.
  639. """
  640. from omegaconf import OmegaConf
  641. object_type = self._metadata.object_type
  642. assert is_structured_config(object_type)
  643. init_field_names = set(get_structured_config_init_field_names(object_type))
  644. init_field_items: Dict[str, Any] = {}
  645. non_init_field_items: Dict[str, Any] = {}
  646. for k in self.keys():
  647. assert isinstance(k, str)
  648. node = self._get_child(k)
  649. assert isinstance(node, Node)
  650. try:
  651. node = node._dereference_node()
  652. except InterpolationResolutionError as e:
  653. self._format_and_raise(key=k, value=None, cause=e)
  654. if node._is_missing():
  655. if k not in init_field_names:
  656. continue # MISSING is ignored for init=False fields
  657. self._format_and_raise(
  658. key=k,
  659. value=None,
  660. cause=MissingMandatoryValue(
  661. "Structured config of type `$OBJECT_TYPE` has missing mandatory value: $KEY"
  662. ),
  663. )
  664. if isinstance(node, Container):
  665. v = OmegaConf.to_object(node)
  666. else:
  667. v = node._value()
  668. if k in init_field_names:
  669. init_field_items[k] = v
  670. else:
  671. non_init_field_items[k] = v
  672. try:
  673. result = object_type(**init_field_items)
  674. except TypeError as exc:
  675. self._format_and_raise(
  676. key=None,
  677. value=None,
  678. cause=exc,
  679. msg="Could not create instance of `$OBJECT_TYPE`: " + str(exc),
  680. )
  681. for k, v in non_init_field_items.items():
  682. setattr(result, k, v)
  683. return result