omegaconf 2.4.0.dev11__tar.gz → 2.4.0.dev12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {omegaconf-2.4.0.dev11/omegaconf.egg-info → omegaconf-2.4.0.dev12}/PKG-INFO +2 -2
  2. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/README.md +1 -1
  3. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/_utils.py +23 -7
  4. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/base.py +27 -1
  5. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/basecontainer.py +58 -21
  6. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/dictconfig.py +0 -3
  7. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/listconfig.py +8 -0
  8. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/omegaconf.py +107 -72
  9. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/version.py +1 -1
  10. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12/omegaconf.egg-info}/PKG-INFO +2 -2
  11. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/pyproject.toml +11 -3
  12. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/setup.cfg +1 -1
  13. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/conftest.py +2 -2
  14. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_create.py +2 -2
  15. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_dict.py +3 -3
  16. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/test_custom_resolvers.py +51 -48
  17. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/test_interpolation.py +3 -3
  18. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/test_structured_config.py +34 -1
  19. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_basic_ops_list.py +20 -0
  20. omegaconf-2.4.0.dev12/tests/test_config_eq.py +429 -0
  21. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_create.py +36 -0
  22. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_errors.py +126 -4
  23. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_grammar.py +6 -6
  24. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_matrix.py +11 -3
  25. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_merge.py +109 -3
  26. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_omegaconf.py +6 -6
  27. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_select.py +11 -3
  28. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_to_container.py +1 -1
  29. omegaconf-2.4.0.dev11/tests/test_config_eq.py +0 -171
  30. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/LICENSE +0 -0
  31. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/MANIFEST.in +0 -0
  32. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/build_helpers/__init__.py +0 -0
  33. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/build_helpers/bin/antlr-4.11.1-complete.jar +0 -0
  34. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/build_helpers/build_helpers.py +0 -0
  35. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/build_helpers/get_vendored.py +0 -0
  36. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/build_helpers/test_helpers.py +0 -0
  37. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/__init__.py +0 -0
  38. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/_impl.py +0 -0
  39. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/_yaml.py +0 -0
  40. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/errors.py +0 -0
  41. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/OmegaConfGrammarLexer.g4 +0 -0
  42. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/OmegaConfGrammarParser.g4 +0 -0
  43. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/__init__.py +0 -0
  44. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/gen/OmegaConfGrammarLexer.py +0 -0
  45. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/gen/OmegaConfGrammarParser.py +0 -0
  46. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py +0 -0
  47. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py +0 -0
  48. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar/gen/__init__.py +0 -0
  49. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar_parser.py +0 -0
  50. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/grammar_visitor.py +0 -0
  51. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/nodes.py +0 -0
  52. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/py.typed +0 -0
  53. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/resolvers/__init__.py +0 -0
  54. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/resolvers/oc/__init__.py +0 -0
  55. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/resolvers/oc/dict.py +0 -0
  56. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/typing.py +0 -0
  57. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/__init__.py +0 -0
  58. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/BufferedTokenStream.py +0 -0
  59. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/CommonTokenFactory.py +0 -0
  60. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/CommonTokenStream.py +0 -0
  61. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/FileStream.py +0 -0
  62. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/InputStream.py +0 -0
  63. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/IntervalSet.py +0 -0
  64. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/LL1Analyzer.py +0 -0
  65. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/Lexer.py +0 -0
  66. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/ListTokenSource.py +0 -0
  67. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/Parser.py +0 -0
  68. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/ParserInterpreter.py +0 -0
  69. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/ParserRuleContext.py +0 -0
  70. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/PredictionContext.py +0 -0
  71. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/Recognizer.py +0 -0
  72. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/RuleContext.py +0 -0
  73. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/StdinStream.py +0 -0
  74. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/Token.py +0 -0
  75. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/TokenStreamRewriter.py +0 -0
  76. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/Utils.py +0 -0
  77. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/__init__.py +0 -0
  78. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/_pygrun.py +0 -0
  79. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATN.py +0 -0
  80. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNConfig.py +0 -0
  81. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNConfigSet.py +0 -0
  82. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNDeserializationOptions.py +0 -0
  83. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNDeserializer.py +0 -0
  84. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNSimulator.py +0 -0
  85. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNState.py +0 -0
  86. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ATNType.py +0 -0
  87. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/LexerATNSimulator.py +0 -0
  88. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/LexerAction.py +0 -0
  89. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/LexerActionExecutor.py +0 -0
  90. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/ParserATNSimulator.py +0 -0
  91. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/PredictionMode.py +0 -0
  92. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/SemanticContext.py +0 -0
  93. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/Transition.py +0 -0
  94. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/atn/__init__.py +0 -0
  95. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/dfa/DFA.py +0 -0
  96. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/dfa/DFASerializer.py +0 -0
  97. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/dfa/DFAState.py +0 -0
  98. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/dfa/__init__.py +0 -0
  99. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/error/DiagnosticErrorListener.py +0 -0
  100. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/error/ErrorListener.py +0 -0
  101. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/error/ErrorStrategy.py +0 -0
  102. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/error/Errors.py +0 -0
  103. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/error/__init__.py +0 -0
  104. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/Chunk.py +0 -0
  105. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/ParseTreeMatch.py +0 -0
  106. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/ParseTreePattern.py +0 -0
  107. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/ParseTreePatternMatcher.py +0 -0
  108. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/RuleTagToken.py +0 -0
  109. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/TokenTagToken.py +0 -0
  110. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/Tree.py +0 -0
  111. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/Trees.py +0 -0
  112. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/tree/__init__.py +0 -0
  113. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/xpath/XPath.py +0 -0
  114. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/xpath/XPathLexer.py +0 -0
  115. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf/vendor/antlr4/xpath/__init__.py +0 -0
  116. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf.egg-info/SOURCES.txt +0 -0
  117. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf.egg-info/dependency_links.txt +0 -0
  118. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf.egg-info/requires.txt +0 -0
  119. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/omegaconf.egg-info/top_level.txt +0 -0
  120. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/requirements/base.txt +0 -0
  121. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/requirements/dev.txt +0 -0
  122. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/requirements/docs.txt +0 -0
  123. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/setup.py +0 -0
  124. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/__init__.py +0 -0
  125. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/data/2.0.6.pickle +0 -0
  126. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/data/2.1.0.rc1.pickle +0 -0
  127. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/data/load.py +0 -0
  128. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/data/save.py +0 -0
  129. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/examples/__init__.py +0 -0
  130. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/examples/dataclass_postponed_annotations.py +0 -0
  131. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/examples/test_dataclass_example.py +0 -0
  132. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/examples/test_postponed_annotations.py +0 -0
  133. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/__init__.py +0 -0
  134. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/__init__.py +0 -0
  135. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_decode.py +0 -0
  136. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_deprecated.py +0 -0
  137. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_env.py +0 -0
  138. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/interpolation/built_in_resolvers/test_oc_select.py +0 -0
  139. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/__init__.py +0 -0
  140. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/data/__init__.py +0 -0
  141. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/data/attr_classes.py +0 -0
  142. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/data/dataclasses.py +0 -0
  143. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/data/dataclasses_pre_311.py +0 -0
  144. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/structured_conf/test_structured_basic.py +0 -0
  145. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_base_config.py +0 -0
  146. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_basic_ops_dict.py +0 -0
  147. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_compare_dictconfig_vs_dict.py +0 -0
  148. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_container_unions.py +0 -0
  149. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_get_full_key.py +0 -0
  150. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_nested_containers.py +0 -0
  151. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_nodes.py +0 -0
  152. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_readonly.py +0 -0
  153. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_serialization.py +0 -0
  154. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_struct.py +0 -0
  155. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_to_yaml.py +0 -0
  156. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_unions.py +0 -0
  157. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_update.py +0 -0
  158. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev12}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omegaconf
3
- Version: 2.4.0.dev11
3
+ Version: 2.4.0.dev12
4
4
  Summary: A flexible configuration library
5
5
  Home-page: https://github.com/omry/omegaconf
6
6
  Author: Omry Yadan
@@ -57,7 +57,7 @@ Dynamic: summary
57
57
  | Project | [![PyPI version](https://badge.fury.io/py/omegaconf.svg)](https://badge.fury.io/py/omegaconf)[![Downloads](https://pepy.tech/badge/omegaconf/month)](https://pepy.tech/project/omegaconf)![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue) |
58
58
  | Code quality| [![CircleCI](https://dl.circleci.com/status-badge/img/gh/omry/omegaconf/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/omry/omegaconf/tree/main)[![Coverage Status](https://coveralls.io/repos/github/omry/omegaconf/badge.svg)](https://coveralls.io/github/omry/omegaconf)|
59
59
  | Docs and support |[![Documentation Status](https://readthedocs.org/projects/omegaconf/badge/?version=2.0_branch)](https://omegaconf.readthedocs.io/en/2.3_branch/)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/omry/omegaconf/master?filepath=docs%2Fnotebook%2FTutorial.ipynb)[![Zulip chat](https://img.shields.io/badge/chat-Zulip-2e77d0?logo=zulip)](https://hydra-framework.zulipchat.com/)|
60
- | Backlog | [Issues & PRs backlog](https://omry.github.io/omegaconf/) |
60
+ | Backlog | [![Backlog Atlas dashboard](https://omry.github.io/backlog-atlas/badge.svg)](https://omry.github.io/backlog-atlas/) |
61
61
 
62
62
 
63
63
  OmegaConf is a hierarchical configuration system, with support for merging configurations from multiple sources (YAML config files, dataclasses/objects and CLI arguments)
@@ -4,7 +4,7 @@
4
4
  | Project | [![PyPI version](https://badge.fury.io/py/omegaconf.svg)](https://badge.fury.io/py/omegaconf)[![Downloads](https://pepy.tech/badge/omegaconf/month)](https://pepy.tech/project/omegaconf)![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue) |
5
5
  | Code quality| [![CircleCI](https://dl.circleci.com/status-badge/img/gh/omry/omegaconf/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/omry/omegaconf/tree/main)[![Coverage Status](https://coveralls.io/repos/github/omry/omegaconf/badge.svg)](https://coveralls.io/github/omry/omegaconf)|
6
6
  | Docs and support |[![Documentation Status](https://readthedocs.org/projects/omegaconf/badge/?version=2.0_branch)](https://omegaconf.readthedocs.io/en/2.3_branch/)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/omry/omegaconf/master?filepath=docs%2Fnotebook%2FTutorial.ipynb)[![Zulip chat](https://img.shields.io/badge/chat-Zulip-2e77d0?logo=zulip)](https://hydra-framework.zulipchat.com/)|
7
- | Backlog | [Issues & PRs backlog](https://omry.github.io/omegaconf/) |
7
+ | Backlog | [![Backlog Atlas dashboard](https://omry.github.io/backlog-atlas/badge.svg)](https://omry.github.io/backlog-atlas/) |
8
8
 
9
9
 
10
10
  OmegaConf is a hierarchical configuration system, with support for merging configurations from multiple sources (YAML config files, dataclasses/objects and CLI arguments)
@@ -340,6 +340,7 @@ def get_attr_class_fields(obj: Any) -> List["AttrAttribute[Any]"]:
340
340
 
341
341
 
342
342
  def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, Any]:
343
+ from omegaconf.base import Node
343
344
  from omegaconf.omegaconf import OmegaConf, _maybe_wrap
344
345
 
345
346
  flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
@@ -377,6 +378,8 @@ def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, A
377
378
  value = default.factory()
378
379
  else:
379
380
  value = default
381
+ if isinstance(value, Node):
382
+ value = copy.deepcopy(value)
380
383
  if is_union_annotation(type_) and not is_supported_union_annotation(type_):
381
384
  e = ConfigValueError(
382
385
  f"Unions of containers are not supported:\n{name}: {type_str(type_)}" # noqa: E231
@@ -410,6 +413,7 @@ def get_dataclass_fields(obj: Any) -> List["dataclasses.Field[Any]"]:
410
413
  def get_dataclass_data(
411
414
  obj: Any, allow_objects: Optional[bool] = None
412
415
  ) -> Dict[str, Any]:
416
+ from omegaconf.base import Node
413
417
  from omegaconf.omegaconf import MISSING, OmegaConf, _maybe_wrap
414
418
 
415
419
  flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
@@ -435,6 +439,8 @@ def get_dataclass_data(
435
439
  value = field.default_factory() # type: ignore
436
440
  else:
437
441
  value = MISSING
442
+ if isinstance(value, Node):
443
+ value = copy.deepcopy(value)
438
444
 
439
445
  if is_union_annotation(type_) and not is_supported_union_annotation(type_):
440
446
  e = ConfigValueError(
@@ -747,7 +753,10 @@ def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
747
753
  if args is None:
748
754
  bases = getattr(ref_type, "__orig_bases__", None)
749
755
  if bases is not None and len(bases) > 0:
750
- args = getattr(bases[0], "__args__", None)
756
+ for base in bases:
757
+ if is_dict_annotation(base):
758
+ args = getattr(base, "__args__", None)
759
+ break
751
760
 
752
761
  key_type: Any
753
762
  element_type: Any
@@ -755,7 +764,7 @@ def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
755
764
  key_type = Any
756
765
  element_type = Any
757
766
  else:
758
- if args is not None:
767
+ if args is not None and len(args) == 2:
759
768
  key_type = args[0]
760
769
  element_type = args[1]
761
770
  else:
@@ -829,12 +838,16 @@ def get_type_hint(obj: Any, key: Any = None) -> Optional[Type[Any]]:
829
838
  return Any # type: ignore
830
839
 
831
840
 
832
- def _raise(ex: Exception, cause: Exception) -> None:
841
+ def _is_full_backtrace_enabled() -> bool:
833
842
  # Set the environment variable OC_CAUSE=1 to get a stacktrace that includes the
834
843
  # causing exception.
835
844
  env_var = os.environ["OC_CAUSE"] if "OC_CAUSE" in os.environ else None
836
845
  debugging = sys.gettrace() is not None
837
- full_backtrace = (debugging and not env_var == "0") or (env_var == "1")
846
+ return (debugging and not env_var == "0") or (env_var == "1")
847
+
848
+
849
+ def _raise(ex: Exception, cause: Exception) -> None:
850
+ full_backtrace = _is_full_backtrace_enabled()
838
851
  if full_backtrace:
839
852
  # In the alias case (`cause is ex`), assigning `ex.__cause__ = cause`
840
853
  # would create a self-reference cycle that pins `ex` (and transitively
@@ -845,9 +858,12 @@ def _raise(ex: Exception, cause: Exception) -> None:
845
858
  else:
846
859
  ex.__cause__ = None
847
860
  try:
848
- raise ex.with_traceback(
849
- sys.exc_info()[2]
850
- ) # set env var OC_CAUSE=1 for full trace
861
+ # Set env var OC_CAUSE=1 for full trace. In normal mode, avoid
862
+ # preserving the active traceback because it can retain caller frame
863
+ # locals until cyclic GC runs.
864
+ if full_backtrace:
865
+ raise ex.with_traceback(sys.exc_info()[2])
866
+ raise ex.with_traceback(None) from None
851
867
  finally:
852
868
  # Follow https://peps.python.org/pep-3110/ to break
853
869
  # the exception reference cycle.
@@ -22,6 +22,7 @@ from ._utils import (
22
22
  NoneType,
23
23
  ValueKind,
24
24
  _get_value,
25
+ _is_full_backtrace_enabled,
25
26
  _is_interpolation,
26
27
  _is_missing_value,
27
28
  _is_special,
@@ -42,6 +43,7 @@ from .errors import (
42
43
  InterpolationToMissingValueError,
43
44
  InterpolationValidationError,
44
45
  MissingMandatoryValue,
46
+ OmegaConfBaseException,
45
47
  UnsupportedInterpolationType,
46
48
  ValidationError,
47
49
  )
@@ -238,6 +240,17 @@ class Node(ABC):
238
240
  msg: Optional[str] = None,
239
241
  type_override: Any = None,
240
242
  ) -> NoReturn:
243
+ if (
244
+ type_override is None
245
+ and isinstance(cause, OmegaConfBaseException)
246
+ and cause._initialized
247
+ ):
248
+ if not _is_full_backtrace_enabled():
249
+ cause.__traceback__ = None
250
+ cause.__context__ = None
251
+ # Keep the re-raise in this common wrapper so callers do not need
252
+ # to remember traceback cleanup before forwarding OmegaConf errors.
253
+ raise
241
254
  format_and_raise(
242
255
  node=self,
243
256
  key=key,
@@ -690,6 +703,7 @@ class Container(Box):
690
703
  resolved_node_cache: Optional[Dict[int, "Node"]] = None,
691
704
  ) -> "Node":
692
705
  """A node interpolation is of the form `${foo.bar}`"""
706
+ original_inter_key = inter_key
693
707
  try:
694
708
  root_node, inter_key = self._resolve_key_and_root(inter_key)
695
709
  except ConfigKeyError as exc:
@@ -711,7 +725,19 @@ class Container(Box):
711
725
  ).with_traceback(sys.exc_info()[2])
712
726
 
713
727
  if parent is None or value is None:
714
- raise InterpolationKeyError(f"Interpolation key '{inter_key}' not found")
728
+ msg = f"Interpolation key '{inter_key}' not found"
729
+ if original_inter_key != inter_key:
730
+ try:
731
+ resolved_inter_key = root_node._get_full_key(inter_key)
732
+ except Exception as exc:
733
+ resolved_inter_key = (
734
+ f"<unresolvable due to {type(exc).__name__}: {exc}>"
735
+ )
736
+ msg = (
737
+ f"Interpolation key '{original_inter_key}' not found"
738
+ f" (resolved to '{resolved_inter_key}')"
739
+ )
740
+ raise InterpolationKeyError(msg)
715
741
  else:
716
742
  self._validate_not_dereferencing_to_parent(node=self, target=value)
717
743
  return value
@@ -359,6 +359,7 @@ class BaseContainer(Container, ABC):
359
359
  dest: "BaseContainer",
360
360
  src: "BaseContainer",
361
361
  list_merge_mode: ListMergeMode = ListMergeMode.REPLACE,
362
+ _allow_readonly_target: bool = False,
362
363
  ) -> None:
363
364
  """merge src into dest and return a new copy, does not modified input"""
364
365
  from omegaconf import AnyNode, DictConfig, ValueNode
@@ -465,6 +466,7 @@ class BaseContainer(Container, ABC):
465
466
  dest_node._merge_with(
466
467
  src_node,
467
468
  list_merge_mode=list_merge_mode,
469
+ _allow_readonly_target=_allow_readonly_target,
468
470
  )
469
471
  elif not src_node_missing:
470
472
  dest.__setitem__(key, src_node)
@@ -581,6 +583,7 @@ class BaseContainer(Container, ABC):
581
583
  "BaseContainer", Dict[str, Any], List[Any], Tuple[Any, ...], Any
582
584
  ],
583
585
  list_merge_mode: ListMergeMode = ListMergeMode.REPLACE,
586
+ _allow_readonly_target: bool = False,
584
587
  ) -> None:
585
588
  from .dictconfig import DictConfig
586
589
  from .listconfig import ListConfig
@@ -594,21 +597,35 @@ class BaseContainer(Container, ABC):
594
597
  if self._get_flag("allow_objects") is True:
595
598
  my_flags = {"allow_objects": True}
596
599
  other = _ensure_container(other, flags=my_flags)
600
+ prev_readonly = self._get_node_flag("readonly")
597
601
 
598
- if isinstance(self, DictConfig) and isinstance(other, DictConfig):
599
- BaseContainer._map_merge(
600
- self,
601
- other,
602
- list_merge_mode=list_merge_mode,
603
- )
604
- elif isinstance(self, ListConfig) and isinstance(other, ListConfig):
605
- BaseContainer._list_merge(
606
- self,
607
- other,
608
- list_merge_mode=list_merge_mode,
609
- )
610
- else:
611
- raise TypeError("Cannot merge DictConfig with ListConfig")
602
+ readonly_overridden = (
603
+ _allow_readonly_target and self._get_flag("readonly") is True
604
+ )
605
+ if readonly_overridden:
606
+ # Non-public merge construction may target readonly containers.
607
+ # Temporarily relax them without changing merge_with() semantics.
608
+ self._set_flag("readonly", False)
609
+
610
+ try:
611
+ if isinstance(self, DictConfig) and isinstance(other, DictConfig):
612
+ BaseContainer._map_merge(
613
+ self,
614
+ other,
615
+ list_merge_mode=list_merge_mode,
616
+ _allow_readonly_target=_allow_readonly_target,
617
+ )
618
+ elif isinstance(self, ListConfig) and isinstance(other, ListConfig):
619
+ BaseContainer._list_merge(
620
+ self,
621
+ other,
622
+ list_merge_mode=list_merge_mode,
623
+ )
624
+ else:
625
+ raise TypeError("Cannot merge DictConfig with ListConfig")
626
+ finally:
627
+ if readonly_overridden:
628
+ self._set_flag("readonly", prev_readonly)
612
629
 
613
630
  # recursively correct the parent hierarchy after the merge
614
631
  self._re_parent()
@@ -917,6 +934,19 @@ def _update_types(node: Node, ref_type: Any, object_type: Optional[type]) -> Non
917
934
  node._metadata.object_type = object_type
918
935
 
919
936
  if node._metadata.ref_type is Any:
937
+ new_is_optional, new_ref_type = _resolve_optional(ref_type)
938
+ if is_dict_annotation(new_ref_type) and is_structured_config(
939
+ node._metadata.object_type
940
+ ):
941
+ from ._utils import get_dict_key_value_types
942
+
943
+ assert isinstance(node._metadata, ContainerMetadata)
944
+ node._metadata.optional = new_is_optional
945
+ node._metadata.ref_type = new_ref_type
946
+ node._metadata.key_type, node._metadata.element_type = (
947
+ get_dict_key_value_types(new_ref_type)
948
+ )
949
+ return
920
950
  _deep_update_type_hint(node, ref_type)
921
951
 
922
952
 
@@ -972,17 +1002,24 @@ def _deep_update_type_hint(node: Node, type_hint: Any) -> None:
972
1002
 
973
1003
  def _deep_update_subnode(node: BaseContainer, key: Any, value_type_hint: Any) -> None:
974
1004
  """Get node[key] and ensure it is compatible with value_type_hint, mutating if necessary."""
1005
+ from .nodes import AnyNode
1006
+
975
1007
  subnode = node._get_node(key)
976
1008
  assert isinstance(subnode, Node)
977
- if _is_special(subnode) or (
978
- is_union_annotation(value_type_hint)
979
- and (
980
- not isinstance(subnode, UnionNode)
981
- or get_type_hint(subnode) != value_type_hint
1009
+ _, ref_type = _resolve_optional(value_type_hint)
1010
+ if (
1011
+ _is_special(subnode)
1012
+ or (isinstance(subnode, AnyNode) and ref_type is not Any)
1013
+ or (
1014
+ is_union_annotation(value_type_hint)
1015
+ and (
1016
+ not isinstance(subnode, UnionNode)
1017
+ or get_type_hint(subnode) != value_type_hint
1018
+ )
982
1019
  )
983
1020
  ):
984
- # Ensure special values are wrapped in a Node subclass that
985
- # is compatible with the type hint.
1021
+ # Ensure dynamically typed or special values are wrapped in a Node
1022
+ # subclass that is compatible with the type hint.
986
1023
  node._wrap_value_and_set(key, subnode._value(), value_type_hint)
987
1024
  subnode = node._get_node(key)
988
1025
  assert isinstance(subnode, Node)
@@ -48,7 +48,6 @@ from .errors import (
48
48
  InterpolationResolutionError,
49
49
  KeyValidationError,
50
50
  MissingMandatoryValue,
51
- OmegaConfBaseException,
52
51
  ReadonlyConfigError,
53
52
  ValidationError,
54
53
  )
@@ -344,8 +343,6 @@ class DictConfig(BaseContainer, MutableMapping[Any, Any]):
344
343
  try:
345
344
  self.__set_impl(key, value)
346
345
  except Exception as e:
347
- if isinstance(e, OmegaConfBaseException) and e._initialized:
348
- raise e
349
346
  self._format_and_raise(key=key, value=value, cause=e)
350
347
  assert False
351
348
 
@@ -15,6 +15,7 @@ from typing import (
15
15
 
16
16
  from ._utils import (
17
17
  ValueKind,
18
+ _get_value,
18
19
  _is_missing_literal,
19
20
  _is_none,
20
21
  _resolve_optional,
@@ -512,9 +513,11 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
512
513
  self.resolve = resolve
513
514
  self.iterator = iter(lst.__dict__["_content"])
514
515
  self.index = 0
516
+ from .base import UnionNode
515
517
  from .nodes import ValueNode
516
518
 
517
519
  self.ValueNode = ValueNode
520
+ self.UnionNode = UnionNode
518
521
 
519
522
  def __next__(self) -> Any:
520
523
  x = next(self.iterator)
@@ -526,6 +529,11 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
526
529
  self.index = self.index + 1
527
530
  if isinstance(x, self.ValueNode):
528
531
  return x._value()
532
+ elif self.resolve and isinstance(x, self.UnionNode):
533
+ # Resolved iteration mirrors indexing and yields the selected
534
+ # concrete value instead of leaking the UnionNode wrapper. The
535
+ # unresolved path keeps the node so copies preserve its type.
536
+ return _get_value(x)
529
537
  else:
530
538
  # Must be omegaconf.Container. not checking for perf reasons.
531
539
  if x._is_none():
@@ -109,13 +109,13 @@ def SI(interpolation: str) -> Any:
109
109
  def register_default_resolvers() -> None:
110
110
  from omegaconf.resolvers import oc
111
111
 
112
- OmegaConf.register_new_resolver("oc.create", oc.create)
113
- OmegaConf.register_new_resolver("oc.decode", oc.decode)
114
- OmegaConf.register_new_resolver("oc.deprecated", oc.deprecated)
115
- OmegaConf.register_new_resolver("oc.env", oc.env)
116
- OmegaConf.register_new_resolver("oc.select", oc.select)
117
- OmegaConf.register_new_resolver("oc.dict.keys", oc.dict.keys)
118
- OmegaConf.register_new_resolver("oc.dict.values", oc.dict.values)
112
+ OmegaConf.register_resolver("oc.create", oc.create)
113
+ OmegaConf.register_resolver("oc.decode", oc.decode)
114
+ OmegaConf.register_resolver("oc.deprecated", oc.deprecated)
115
+ OmegaConf.register_resolver("oc.env", oc.env)
116
+ OmegaConf.register_resolver("oc.select", oc.select)
117
+ OmegaConf.register_resolver("oc.dict.keys", oc.dict.keys)
118
+ OmegaConf.register_resolver("oc.dict.values", oc.dict.values)
119
119
 
120
120
 
121
121
  class OmegaConf:
@@ -417,6 +417,9 @@ class OmegaConf:
417
417
  """
418
418
  Merge a list of previously created configs into a single one
419
419
 
420
+ Note for maintainers: changes to merge behavior should also consider
421
+ whether OmegaConf.unsafe_merge() needs the same coverage.
422
+
420
423
  :param configs: Input configs
421
424
  :param list_merge_mode: Behavior for merging lists
422
425
  REPLACE: Replaces the target list with the new one (default)
@@ -430,15 +433,11 @@ class OmegaConf:
430
433
  target = _ensure_container(target)
431
434
  assert isinstance(target, (DictConfig, ListConfig))
432
435
 
433
- with flag_override(target, "readonly", False):
434
- target.merge_with(
435
- *configs[1:],
436
- list_merge_mode=list_merge_mode,
437
- )
438
- turned_readonly = target._get_flag("readonly") is True
439
-
440
- if turned_readonly:
441
- OmegaConf.set_readonly(target, True)
436
+ target._merge_with(
437
+ *configs[1:],
438
+ list_merge_mode=list_merge_mode,
439
+ _allow_readonly_target=True,
440
+ )
442
441
 
443
442
  return target
444
443
 
@@ -475,9 +474,10 @@ class OmegaConf:
475
474
  with flag_override(
476
475
  target, ["readonly", "no_deepcopy_set_nodes"], [False, True]
477
476
  ):
478
- target.merge_with(
477
+ target._merge_with(
479
478
  *configs[1:],
480
479
  list_merge_mode=list_merge_mode,
480
+ _allow_readonly_target=True,
481
481
  )
482
482
  turned_readonly = target._get_flag("readonly") is True
483
483
 
@@ -487,61 +487,7 @@ class OmegaConf:
487
487
  return target
488
488
 
489
489
  @staticmethod
490
- def register_resolver(name: str, resolver: Resolver) -> None:
491
- warnings.warn(
492
- dedent("""\
493
- register_resolver() is deprecated.
494
- See https://github.com/omry/omegaconf/issues/426 for migration instructions.
495
- """),
496
- stacklevel=2,
497
- )
498
- return OmegaConf.legacy_register_resolver(name, resolver)
499
-
500
- # This function will eventually be deprecated and removed.
501
- @staticmethod
502
- def legacy_register_resolver(name: str, resolver: Resolver) -> None:
503
- assert callable(resolver), "resolver must be callable"
504
- # noinspection PyProtectedMember
505
- assert (
506
- name not in BaseContainer._resolvers
507
- ), f"resolver '{name}' is already registered"
508
-
509
- def resolver_wrapper(
510
- config: BaseContainer,
511
- parent: BaseContainer,
512
- node: Node,
513
- args: Tuple[Any, ...],
514
- args_str: Tuple[str, ...],
515
- ) -> Any:
516
- cache = OmegaConf.get_cache(config)[name]
517
- # "Un-escape " spaces and commas.
518
- args_unesc = [x.replace(r"\ ", " ").replace(r"\,", ",") for x in args_str]
519
-
520
- # Nested interpolations behave in a potentially surprising way with
521
- # legacy resolvers (they remain as strings, e.g., "${foo}"). If any
522
- # input looks like an interpolation we thus raise an exception.
523
- try:
524
- bad_arg = next(i for i in args_unesc if "${" in i)
525
- except StopIteration:
526
- pass
527
- else:
528
- raise ValueError(
529
- f"Resolver '{name}' was called with argument '{bad_arg}' that appears "
530
- f"to be an interpolation. Nested interpolations are not supported for "
531
- f"resolvers registered with `[legacy_]register_resolver()`, please use "
532
- f"`register_new_resolver()` instead (see "
533
- f"https://github.com/omry/omegaconf/issues/426 for migration instructions)." # noqa: E231
534
- )
535
- key = args_str
536
- val = cache[key] if key in cache else resolver(*args_unesc)
537
- cache[key] = val
538
- return val
539
-
540
- # noinspection PyProtectedMember
541
- BaseContainer._resolvers[name] = resolver_wrapper
542
-
543
- @staticmethod
544
- def register_new_resolver(
490
+ def register_resolver(
545
491
  name: str,
546
492
  resolver: Resolver,
547
493
  *,
@@ -621,6 +567,76 @@ class OmegaConf:
621
567
  # noinspection PyProtectedMember
622
568
  BaseContainer._resolvers[name] = resolver_wrapper
623
569
 
570
+ @staticmethod
571
+ def register_new_resolver(
572
+ name: str,
573
+ resolver: Resolver,
574
+ *,
575
+ replace: bool = False,
576
+ use_cache: bool = False,
577
+ ) -> None:
578
+ warnings.warn(
579
+ dedent("""\
580
+ register_new_resolver() is deprecated and will be removed in a future release.
581
+ Use register_resolver() instead.
582
+ See https://github.com/omry/omegaconf/issues/426 for migration instructions.
583
+ """),
584
+ stacklevel=2,
585
+ )
586
+ return OmegaConf.register_resolver(
587
+ name, resolver, replace=replace, use_cache=use_cache
588
+ )
589
+
590
+ @staticmethod
591
+ def legacy_register_resolver(name: str, resolver: Resolver) -> None:
592
+ warnings.warn(
593
+ dedent("""\
594
+ legacy_register_resolver() is deprecated and will be removed in a future release.
595
+ Use register_resolver() instead.
596
+ See https://github.com/omry/omegaconf/issues/426 for migration instructions.
597
+ """),
598
+ stacklevel=2,
599
+ )
600
+ assert callable(resolver), "resolver must be callable"
601
+ # noinspection PyProtectedMember
602
+ assert (
603
+ name not in BaseContainer._resolvers
604
+ ), f"resolver '{name}' is already registered"
605
+
606
+ def resolver_wrapper(
607
+ config: BaseContainer,
608
+ parent: BaseContainer,
609
+ node: Node,
610
+ args: Tuple[Any, ...],
611
+ args_str: Tuple[str, ...],
612
+ ) -> Any:
613
+ cache = OmegaConf.get_cache(config)[name]
614
+ # "Un-escape " spaces and commas.
615
+ args_unesc = [x.replace(r"\ ", " ").replace(r"\,", ",") for x in args_str]
616
+
617
+ # Nested interpolations behave in a potentially surprising way with
618
+ # legacy resolvers (they remain as strings, e.g., "${foo}"). If any
619
+ # input looks like an interpolation we thus raise an exception.
620
+ try:
621
+ bad_arg = next(i for i in args_unesc if "${" in i)
622
+ except StopIteration:
623
+ pass
624
+ else:
625
+ raise ValueError(
626
+ f"Resolver '{name}' was called with argument '{bad_arg}' that appears "
627
+ f"to be an interpolation. Nested interpolations are not supported for "
628
+ f"resolvers registered with `legacy_register_resolver()`, please use "
629
+ f"`register_resolver()` instead (see "
630
+ f"https://github.com/omry/omegaconf/issues/426 for migration instructions)." # noqa: E231
631
+ )
632
+ key = args_str
633
+ val = cache[key] if key in cache else resolver(*args_unesc)
634
+ cache[key] = val
635
+ return val
636
+
637
+ # noinspection PyProtectedMember
638
+ BaseContainer._resolvers[name] = resolver_wrapper
639
+
624
640
  @classmethod
625
641
  def has_resolver(cls, name: str) -> bool:
626
642
  """
@@ -808,6 +824,25 @@ class OmegaConf:
808
824
  structured_config_mode=structured_config_mode,
809
825
  )
810
826
 
827
+ @staticmethod
828
+ def structural_equality(cfg1: Any, cfg2: Any) -> bool:
829
+ """
830
+ Compare two configs by their unresolved container structure.
831
+
832
+ This is equivalent to converting both configs with
833
+ ``OmegaConf.to_container(resolve=False, throw_on_missing=False)`` and
834
+ comparing the resulting containers. Interpolations and custom resolver
835
+ expressions are compared as their raw strings and are not resolved.
836
+ Missing values do not raise.
837
+
838
+ :param cfg1: First OmegaConf config to compare.
839
+ :param cfg2: Second OmegaConf config to compare.
840
+ :return: ``True`` if both configs have the same unresolved structure.
841
+ """
842
+ return OmegaConf.to_container(
843
+ cfg1, resolve=False, throw_on_missing=False
844
+ ) == OmegaConf.to_container(cfg2, resolve=False, throw_on_missing=False)
845
+
811
846
  @staticmethod
812
847
  def to_object(cfg: Any) -> Union[Dict[DictKeyType, Any], List[Any], None, str, Any]:
813
848
  """
@@ -1,6 +1,6 @@
1
1
  import sys # pragma: no cover
2
2
 
3
- __version__ = "2.4.0.dev11"
3
+ __version__ = "2.4.0.dev12"
4
4
 
5
5
  msg = """OmegaConf 2.4 and above is compatible with Python 3.10 and newer.
6
6
  You have the following options:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omegaconf
3
- Version: 2.4.0.dev11
3
+ Version: 2.4.0.dev12
4
4
  Summary: A flexible configuration library
5
5
  Home-page: https://github.com/omry/omegaconf
6
6
  Author: Omry Yadan
@@ -57,7 +57,7 @@ Dynamic: summary
57
57
  | Project | [![PyPI version](https://badge.fury.io/py/omegaconf.svg)](https://badge.fury.io/py/omegaconf)[![Downloads](https://pepy.tech/badge/omegaconf/month)](https://pepy.tech/project/omegaconf)![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue) |
58
58
  | Code quality| [![CircleCI](https://dl.circleci.com/status-badge/img/gh/omry/omegaconf/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/omry/omegaconf/tree/main)[![Coverage Status](https://coveralls.io/repos/github/omry/omegaconf/badge.svg)](https://coveralls.io/github/omry/omegaconf)|
59
59
  | Docs and support |[![Documentation Status](https://readthedocs.org/projects/omegaconf/badge/?version=2.0_branch)](https://omegaconf.readthedocs.io/en/2.3_branch/)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/omry/omegaconf/master?filepath=docs%2Fnotebook%2FTutorial.ipynb)[![Zulip chat](https://img.shields.io/badge/chat-Zulip-2e77d0?logo=zulip)](https://hydra-framework.zulipchat.com/)|
60
- | Backlog | [Issues & PRs backlog](https://omry.github.io/omegaconf/) |
60
+ | Backlog | [![Backlog Atlas dashboard](https://omry.github.io/backlog-atlas/badge.svg)](https://omry.github.io/backlog-atlas/) |
61
61
 
62
62
 
63
63
  OmegaConf is a hierarchical configuration system, with support for merging configurations from multiple sources (YAML config files, dataclasses/objects and CLI arguments)
@@ -7,6 +7,7 @@ exclude = '''
7
7
  | omegaconf/grammar/gen
8
8
  | omegaconf/vendor
9
9
  | \.nox
10
+ | \.venv
10
11
  | build
11
12
  | subprojects
12
13
  | temp
@@ -17,15 +18,21 @@ exclude = '''
17
18
  '''
18
19
 
19
20
  [tool.isort]
20
- skip_glob = ["temp/*", "omegaconf/vendor/*", "subprojects/*", ".claude/*"]
21
+ skip_glob = [
22
+ ".venv/*",
23
+ "temp/*",
24
+ "omegaconf/vendor/*",
25
+ "subprojects/*",
26
+ ".claude/*",
27
+ ]
21
28
 
22
29
  [tool.pytest.ini_options]
23
30
  addopts = "--import-mode=append -Werror"
24
31
  pythonpath = ["."]
25
- norecursedirs = ["subprojects", ".git", ".sl"]
32
+ norecursedirs = ["subprojects", ".git", ".sl", ".venv"]
26
33
 
27
34
  [tool.bumpversion]
28
- current_version = "2.4.0.dev11"
35
+ current_version = "2.4.0.dev12"
29
36
  parse = '''(?x)
30
37
  (?P<major>\d+)
31
38
  \.(?P<minor>\d+)
@@ -102,6 +109,7 @@ project-excludes = [
102
109
  "build/**",
103
110
  "temp/**",
104
111
  ".nox/**",
112
+ ".venv/**",
105
113
  "omegaconf/vendor/**",
106
114
  "subprojects/**",
107
115
  ]
@@ -4,7 +4,7 @@ test = pytest
4
4
  [flake8]
5
5
  max-line-length = 88
6
6
  extend-ignore = E203, E501
7
- exclude = .git,.eggs,.nox,build,temp,vendor,subprojects,omegaconf/grammar/gen,omegaconf/vendor
7
+ exclude = .git,.eggs,.nox,.venv,build,temp,vendor,subprojects,omegaconf/grammar/gen,omegaconf/vendor
8
8
 
9
9
  [egg_info]
10
10
  tag_build =