omegaconf 2.4.0.dev11__tar.gz → 2.4.0.dev13__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 (163) hide show
  1. {omegaconf-2.4.0.dev11/omegaconf.egg-info → omegaconf-2.4.0.dev13}/PKG-INFO +2 -2
  2. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/README.md +1 -1
  3. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/__init__.py +2 -0
  4. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/_impl.py +20 -4
  5. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/_utils.py +75 -11
  6. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/base.py +112 -38
  7. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/basecontainer.py +134 -32
  8. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/dictconfig.py +0 -3
  9. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/listconfig.py +31 -5
  10. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/omegaconf.py +224 -102
  11. omegaconf-2.4.0.dev13/omegaconf/tupleconfig.py +522 -0
  12. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/version.py +2 -1
  13. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13/omegaconf.egg-info}/PKG-INFO +2 -2
  14. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/SOURCES.txt +5 -0
  15. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/pyproject.toml +14 -3
  16. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/setup.cfg +1 -1
  17. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/conftest.py +2 -2
  18. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_create.py +2 -2
  19. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_dict.py +3 -3
  20. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/test_custom_resolvers.py +51 -48
  21. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/test_interpolation.py +3 -3
  22. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/test_structured_config.py +69 -9
  23. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_base_config.py +1 -1
  24. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_basic_ops_list.py +56 -4
  25. omegaconf-2.4.0.dev13/tests/test_basic_ops_tuple.py +411 -0
  26. omegaconf-2.4.0.dev13/tests/test_config_eq.py +429 -0
  27. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_container_unions.py +86 -2
  28. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_create.py +46 -2
  29. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_errors.py +134 -6
  30. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_grammar.py +6 -6
  31. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_matrix.py +11 -3
  32. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_merge.py +113 -8
  33. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_nested_containers.py +20 -8
  34. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_omegaconf.py +6 -6
  35. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_select.py +11 -3
  36. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_to_container.py +1 -1
  37. omegaconf-2.4.0.dev13/tests/test_tuple_integration.py +186 -0
  38. omegaconf-2.4.0.dev13/tests/test_tuple_merge.py +178 -0
  39. omegaconf-2.4.0.dev13/tests/test_tuple_structured.py +205 -0
  40. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_utils.py +4 -1
  41. omegaconf-2.4.0.dev11/tests/test_config_eq.py +0 -171
  42. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/LICENSE +0 -0
  43. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/MANIFEST.in +0 -0
  44. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/build_helpers/__init__.py +0 -0
  45. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/build_helpers/bin/antlr-4.11.1-complete.jar +0 -0
  46. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/build_helpers/build_helpers.py +0 -0
  47. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/build_helpers/get_vendored.py +0 -0
  48. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/build_helpers/test_helpers.py +0 -0
  49. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/_yaml.py +0 -0
  50. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/errors.py +0 -0
  51. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/OmegaConfGrammarLexer.g4 +0 -0
  52. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/OmegaConfGrammarParser.g4 +0 -0
  53. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/__init__.py +0 -0
  54. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarLexer.py +0 -0
  55. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParser.py +0 -0
  56. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py +0 -0
  57. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py +0 -0
  58. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/__init__.py +0 -0
  59. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar_parser.py +0 -0
  60. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/grammar_visitor.py +0 -0
  61. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/nodes.py +0 -0
  62. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/py.typed +0 -0
  63. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/__init__.py +0 -0
  64. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/oc/__init__.py +0 -0
  65. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/oc/dict.py +0 -0
  66. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/typing.py +0 -0
  67. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/__init__.py +0 -0
  68. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/BufferedTokenStream.py +0 -0
  69. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/CommonTokenFactory.py +0 -0
  70. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/CommonTokenStream.py +0 -0
  71. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/FileStream.py +0 -0
  72. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/InputStream.py +0 -0
  73. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/IntervalSet.py +0 -0
  74. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/LL1Analyzer.py +0 -0
  75. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Lexer.py +0 -0
  76. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ListTokenSource.py +0 -0
  77. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Parser.py +0 -0
  78. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ParserInterpreter.py +0 -0
  79. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ParserRuleContext.py +0 -0
  80. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/PredictionContext.py +0 -0
  81. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Recognizer.py +0 -0
  82. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/RuleContext.py +0 -0
  83. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/StdinStream.py +0 -0
  84. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Token.py +0 -0
  85. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/TokenStreamRewriter.py +0 -0
  86. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Utils.py +0 -0
  87. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/__init__.py +0 -0
  88. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/_pygrun.py +0 -0
  89. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATN.py +0 -0
  90. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNConfig.py +0 -0
  91. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNConfigSet.py +0 -0
  92. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNDeserializationOptions.py +0 -0
  93. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNDeserializer.py +0 -0
  94. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNSimulator.py +0 -0
  95. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNState.py +0 -0
  96. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNType.py +0 -0
  97. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerATNSimulator.py +0 -0
  98. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerAction.py +0 -0
  99. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerActionExecutor.py +0 -0
  100. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ParserATNSimulator.py +0 -0
  101. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/PredictionMode.py +0 -0
  102. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/SemanticContext.py +0 -0
  103. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/Transition.py +0 -0
  104. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/__init__.py +0 -0
  105. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFA.py +0 -0
  106. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFASerializer.py +0 -0
  107. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFAState.py +0 -0
  108. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/__init__.py +0 -0
  109. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/DiagnosticErrorListener.py +0 -0
  110. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/ErrorListener.py +0 -0
  111. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/ErrorStrategy.py +0 -0
  112. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/Errors.py +0 -0
  113. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/__init__.py +0 -0
  114. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Chunk.py +0 -0
  115. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreeMatch.py +0 -0
  116. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreePattern.py +0 -0
  117. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreePatternMatcher.py +0 -0
  118. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/RuleTagToken.py +0 -0
  119. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/TokenTagToken.py +0 -0
  120. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Tree.py +0 -0
  121. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Trees.py +0 -0
  122. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/__init__.py +0 -0
  123. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/XPath.py +0 -0
  124. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/XPathLexer.py +0 -0
  125. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/__init__.py +0 -0
  126. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/dependency_links.txt +0 -0
  127. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/requires.txt +0 -0
  128. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/top_level.txt +0 -0
  129. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/requirements/base.txt +0 -0
  130. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/requirements/dev.txt +0 -0
  131. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/requirements/docs.txt +0 -0
  132. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/setup.py +0 -0
  133. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/__init__.py +0 -0
  134. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/data/2.0.6.pickle +0 -0
  135. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/data/2.1.0.rc1.pickle +0 -0
  136. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/data/load.py +0 -0
  137. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/data/save.py +0 -0
  138. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/examples/__init__.py +0 -0
  139. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/examples/dataclass_postponed_annotations.py +0 -0
  140. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/examples/test_dataclass_example.py +0 -0
  141. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/examples/test_postponed_annotations.py +0 -0
  142. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/__init__.py +0 -0
  143. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/__init__.py +0 -0
  144. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_decode.py +0 -0
  145. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_deprecated.py +0 -0
  146. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_env.py +0 -0
  147. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_select.py +0 -0
  148. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/__init__.py +0 -0
  149. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/__init__.py +0 -0
  150. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/attr_classes.py +0 -0
  151. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/dataclasses.py +0 -0
  152. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/dataclasses_pre_311.py +0 -0
  153. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/structured_conf/test_structured_basic.py +0 -0
  154. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_basic_ops_dict.py +0 -0
  155. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_compare_dictconfig_vs_dict.py +0 -0
  156. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_get_full_key.py +0 -0
  157. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_nodes.py +0 -0
  158. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_readonly.py +0 -0
  159. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_serialization.py +0 -0
  160. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_struct.py +0 -0
  161. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_to_yaml.py +0 -0
  162. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_unions.py +0 -0
  163. {omegaconf-2.4.0.dev11 → omegaconf-2.4.0.dev13}/tests/test_update.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.dev13
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)
@@ -30,6 +30,7 @@ from .omegaconf import (
30
30
  open_dict,
31
31
  read_write,
32
32
  )
33
+ from .tupleconfig import TupleConfig
33
34
  from .typing import Antlr4ParserRuleContext
34
35
  from .version import __version__
35
36
 
@@ -43,6 +44,7 @@ __all__ = [
43
44
  "Container",
44
45
  "UnionNode",
45
46
  "ListConfig",
47
+ "TupleConfig",
46
48
  "DictConfig",
47
49
  "DictKeyType",
48
50
  "OmegaConf",
@@ -1,13 +1,18 @@
1
1
  from typing import Any
2
2
 
3
- from omegaconf import Container, DictConfig, ListConfig, Node, ValueNode
4
- from omegaconf.errors import ConfigKeyError, ConfigTypeError
3
+ from omegaconf import Container, DictConfig, ListConfig, Node, TupleConfig, ValueNode
4
+ from omegaconf.errors import (
5
+ ConfigKeyError,
6
+ ConfigTypeError,
7
+ InterpolationToMissingValueError,
8
+ )
5
9
  from omegaconf.nodes import InterpolationResultNode
6
10
 
7
11
  from ._utils import (
8
12
  _DEFAULT_MARKER_,
9
13
  _ensure_container,
10
14
  _get_value,
15
+ _is_missing_literal,
11
16
  is_primitive_container,
12
17
  is_structured_config,
13
18
  )
@@ -26,8 +31,19 @@ def _resolve_container_value(cfg: Container, key: Any) -> None:
26
31
  resolved_value
27
32
  ):
28
33
  resolved = _ensure_container(resolved_value)
34
+ if isinstance(cfg, TupleConfig) and _is_missing_literal(_get_value(resolved)):
35
+ cfg._format_and_raise(
36
+ key=key,
37
+ value=_get_value(resolved),
38
+ cause=InterpolationToMissingValueError(
39
+ "TupleConfig interpolation resolved to a missing value"
40
+ ),
41
+ )
29
42
  if isinstance(resolved, Container) and isinstance(node, ValueNode):
30
- cfg[key] = resolved
43
+ if isinstance(cfg, TupleConfig):
44
+ cfg._set_item_for_resolution(key, resolved)
45
+ else:
46
+ cfg[key] = resolved
31
47
  else:
32
48
  node._set_value(_get_value(resolved))
33
49
  else:
@@ -44,7 +60,7 @@ def _resolve(cfg: Node) -> Node:
44
60
  for k in list(cfg.keys()):
45
61
  _resolve_container_value(cfg, k)
46
62
 
47
- elif isinstance(cfg, ListConfig):
63
+ elif isinstance(cfg, (ListConfig, TupleConfig)):
48
64
  for i in range(len(cfg)):
49
65
  _resolve_container_value(cfg, i)
50
66
 
@@ -131,6 +131,7 @@ _DEFAULT_MARKER_: Any = Marker("_DEFAULT_MARKER_")
131
131
  class OmegaConfDumper(BaseDumper): # type: ignore
132
132
  str_representer_added = False
133
133
  pathlib_representers_added = False
134
+ tuple_representer_added = False
134
135
 
135
136
  @staticmethod
136
137
  def str_representer(dumper: yaml.Dumper, data: str) -> yaml.ScalarNode:
@@ -166,6 +167,12 @@ class OmegaConfDumper(BaseDumper): # type: ignore
166
167
  [str(data)],
167
168
  )
168
169
 
170
+ @staticmethod
171
+ def tuple_representer(dumper: yaml.Dumper, data: Any) -> yaml.Node:
172
+ return dumper.represent_sequence(
173
+ yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, data
174
+ )
175
+
169
176
 
170
177
  def get_omega_conf_dumper() -> Type[OmegaConfDumper]:
171
178
  if not OmegaConfDumper.str_representer_added:
@@ -186,6 +193,10 @@ def get_omega_conf_dumper() -> Type[OmegaConfDumper]:
186
193
  )
187
194
  OmegaConfDumper.pathlib_representers_added = True
188
195
 
196
+ if not OmegaConfDumper.tuple_representer_added:
197
+ OmegaConfDumper.add_representer(tuple, OmegaConfDumper.tuple_representer)
198
+ OmegaConfDumper.tuple_representer_added = True
199
+
189
200
  return OmegaConfDumper
190
201
 
191
202
 
@@ -340,6 +351,7 @@ def get_attr_class_fields(obj: Any) -> List["AttrAttribute[Any]"]:
340
351
 
341
352
 
342
353
  def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, Any]:
354
+ from omegaconf.base import Node
343
355
  from omegaconf.omegaconf import OmegaConf, _maybe_wrap
344
356
 
345
357
  flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
@@ -377,6 +389,8 @@ def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, A
377
389
  value = default.factory()
378
390
  else:
379
391
  value = default
392
+ if isinstance(value, Node):
393
+ value = copy.deepcopy(value)
380
394
  if is_union_annotation(type_) and not is_supported_union_annotation(type_):
381
395
  e = ConfigValueError(
382
396
  f"Unions of containers are not supported:\n{name}: {type_str(type_)}" # noqa: E231
@@ -410,6 +424,7 @@ def get_dataclass_fields(obj: Any) -> List["dataclasses.Field[Any]"]:
410
424
  def get_dataclass_data(
411
425
  obj: Any, allow_objects: Optional[bool] = None
412
426
  ) -> Dict[str, Any]:
427
+ from omegaconf.base import Node
413
428
  from omegaconf.omegaconf import MISSING, OmegaConf, _maybe_wrap
414
429
 
415
430
  flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
@@ -435,6 +450,8 @@ def get_dataclass_data(
435
450
  value = field.default_factory() # type: ignore
436
451
  else:
437
452
  value = MISSING
453
+ if isinstance(value, Node):
454
+ value = copy.deepcopy(value)
438
455
 
439
456
  if is_union_annotation(type_) and not is_supported_union_annotation(type_):
440
457
  e = ConfigValueError(
@@ -736,18 +753,54 @@ def get_list_element_type(ref_type: Optional[Type[Any]]) -> Any:
736
753
 
737
754
  def get_tuple_item_types(ref_type: Type[Any]) -> Tuple[Any, ...]:
738
755
  args = getattr(ref_type, "__args__", None)
739
- if args in (None, ()):
756
+ if ref_type in (tuple, Tuple) or args is None:
740
757
  args = (Any, ...)
758
+ elif args == ((),):
759
+ args = ()
741
760
  assert isinstance(args, tuple)
742
761
  return args
743
762
 
744
763
 
764
+ def normalize_tuple_annotation(ref_type: Any) -> Any:
765
+ if not is_tuple_annotation(ref_type):
766
+ raise ValidationError(f"Unsupported tuple type: '{type_str(ref_type)}'")
767
+
768
+ if ref_type in (tuple, Tuple):
769
+ return Tuple[Any, ...]
770
+
771
+ args = get_tuple_item_types(ref_type)
772
+ if Ellipsis in args and not (len(args) == 2 and args[1] is Ellipsis):
773
+ raise ValidationError(f"Unsupported tuple type: '{type_str(ref_type)}'")
774
+
775
+ for item_type in args:
776
+ origin = getattr(item_type, "__origin__", None)
777
+ if (
778
+ getattr(origin, "__module__", None),
779
+ getattr(origin, "__qualname__", None),
780
+ ) in (("typing", "Unpack"), ("typing_extensions", "Unpack")):
781
+ raise ValidationError(f"Unsupported tuple type: '{type_str(ref_type)}'")
782
+
783
+ return ref_type
784
+
785
+
786
+ def is_variadic_tuple_annotation(ref_type: Any) -> bool:
787
+ args = get_tuple_item_types(normalize_tuple_annotation(ref_type))
788
+ return len(args) == 2 and args[1] is Ellipsis
789
+
790
+
791
+ def make_tuple_annotation(item_types: Tuple[Any, ...]) -> Any:
792
+ return Tuple[item_types] # type: ignore[valid-type]
793
+
794
+
745
795
  def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
746
796
  args = getattr(ref_type, "__args__", None)
747
797
  if args is None:
748
798
  bases = getattr(ref_type, "__orig_bases__", None)
749
799
  if bases is not None and len(bases) > 0:
750
- args = getattr(bases[0], "__args__", None)
800
+ for base in bases:
801
+ if is_dict_annotation(base):
802
+ args = getattr(base, "__args__", None)
803
+ break
751
804
 
752
805
  key_type: Any
753
806
  element_type: Any
@@ -755,7 +808,7 @@ def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
755
808
  key_type = Any
756
809
  element_type = Any
757
810
  else:
758
- if args is not None:
811
+ if args is not None and len(args) == 2:
759
812
  key_type = args[0]
760
813
  element_type = args[1]
761
814
  else:
@@ -829,12 +882,16 @@ def get_type_hint(obj: Any, key: Any = None) -> Optional[Type[Any]]:
829
882
  return Any # type: ignore
830
883
 
831
884
 
832
- def _raise(ex: Exception, cause: Exception) -> None:
885
+ def _is_full_backtrace_enabled() -> bool:
833
886
  # Set the environment variable OC_CAUSE=1 to get a stacktrace that includes the
834
887
  # causing exception.
835
888
  env_var = os.environ["OC_CAUSE"] if "OC_CAUSE" in os.environ else None
836
889
  debugging = sys.gettrace() is not None
837
- full_backtrace = (debugging and not env_var == "0") or (env_var == "1")
890
+ return (debugging and not env_var == "0") or (env_var == "1")
891
+
892
+
893
+ def _raise(ex: Exception, cause: Exception) -> None:
894
+ full_backtrace = _is_full_backtrace_enabled()
838
895
  if full_backtrace:
839
896
  # In the alias case (`cause is ex`), assigning `ex.__cause__ = cause`
840
897
  # would create a self-reference cycle that pins `ex` (and transitively
@@ -845,9 +902,12 @@ def _raise(ex: Exception, cause: Exception) -> None:
845
902
  else:
846
903
  ex.__cause__ = None
847
904
  try:
848
- raise ex.with_traceback(
849
- sys.exc_info()[2]
850
- ) # set env var OC_CAUSE=1 for full trace
905
+ # Set env var OC_CAUSE=1 for full trace. In normal mode, avoid
906
+ # preserving the active traceback because it can retain caller frame
907
+ # locals until cyclic GC runs.
908
+ if full_backtrace:
909
+ raise ex.with_traceback(sys.exc_info()[2])
910
+ raise ex.with_traceback(None) from None
851
911
  finally:
852
912
  # Follow https://peps.python.org/pep-3110/ to break
853
913
  # the exception reference cycle.
@@ -1027,14 +1087,14 @@ def _ensure_container(target: Any, flags: Optional[Dict[str, bool]] = None) -> A
1027
1087
  from omegaconf import OmegaConf
1028
1088
 
1029
1089
  if is_primitive_container(target):
1030
- assert isinstance(target, (list, dict))
1090
+ assert isinstance(target, (list, tuple, dict))
1031
1091
  target = OmegaConf.create(target, flags=flags)
1032
1092
  elif is_structured_config(target):
1033
1093
  target = OmegaConf.structured(target, flags=flags)
1034
1094
  elif not OmegaConf.is_config(target):
1035
1095
  raise ValueError(
1036
1096
  "Invalid input. Supports one of "
1037
- + "[dict,list,DictConfig,ListConfig,dataclass,dataclass instance,attr class,attr class instance]"
1097
+ + "[dict,list,tuple,DictConfig,ListConfig,TupleConfig,dataclass,dataclass instance,attr class,attr class instance]"
1038
1098
  )
1039
1099
 
1040
1100
  return target
@@ -1067,7 +1127,11 @@ def is_generic_dict(type_: Any) -> bool:
1067
1127
 
1068
1128
 
1069
1129
  def is_container_annotation(type_: Any) -> bool:
1070
- return is_list_annotation(type_) or is_dict_annotation(type_)
1130
+ return (
1131
+ is_list_annotation(type_)
1132
+ or is_tuple_annotation(type_)
1133
+ or is_dict_annotation(type_)
1134
+ )
1071
1135
 
1072
1136
 
1073
1137
  # Characters recognised after a backslash in key paths and dotlist entries.
@@ -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,
@@ -29,6 +30,7 @@ from ._utils import (
29
30
  get_value_kind,
30
31
  is_dict_annotation,
31
32
  is_list_annotation,
33
+ is_tuple_annotation,
32
34
  is_union_annotation,
33
35
  is_valid_value_annotation,
34
36
  split_key,
@@ -42,6 +44,7 @@ from .errors import (
42
44
  InterpolationToMissingValueError,
43
45
  InterpolationValidationError,
44
46
  MissingMandatoryValue,
47
+ OmegaConfBaseException,
45
48
  UnsupportedInterpolationType,
46
49
  ValidationError,
47
50
  )
@@ -238,6 +241,17 @@ class Node(ABC):
238
241
  msg: Optional[str] = None,
239
242
  type_override: Any = None,
240
243
  ) -> NoReturn:
244
+ if (
245
+ type_override is None
246
+ and isinstance(cause, OmegaConfBaseException)
247
+ and cause._initialized
248
+ ):
249
+ if not _is_full_backtrace_enabled():
250
+ cause.__traceback__ = None
251
+ cause.__context__ = None
252
+ # Keep the re-raise in this common wrapper so callers do not need
253
+ # to remember traceback cleanup before forwarding OmegaConf errors.
254
+ raise
241
255
  format_and_raise(
242
256
  node=self,
243
257
  key=key,
@@ -364,7 +378,7 @@ class Node(ABC):
364
378
  class Box(Node):
365
379
  """
366
380
  Base class for nodes that can contain other nodes.
367
- Concrete subclasses include DictConfig, ListConfig, and UnionNode.
381
+ Concrete subclasses include DictConfig, ListConfig, TupleConfig, and UnionNode.
368
382
  """
369
383
 
370
384
  _content: Any
@@ -380,6 +394,7 @@ class Box(Node):
380
394
  def _re_parent(self) -> None:
381
395
  from .dictconfig import DictConfig
382
396
  from .listconfig import ListConfig
397
+ from .tupleconfig import TupleConfig
383
398
 
384
399
  # update parents of first level Config nodes to self
385
400
 
@@ -391,7 +406,7 @@ class Box(Node):
391
406
  value._set_parent(self)
392
407
  if isinstance(value, Box):
393
408
  value._re_parent()
394
- elif isinstance(self, ListConfig):
409
+ elif isinstance(self, (ListConfig, TupleConfig)):
395
410
  content = self.__dict__["_content"]
396
411
  if isinstance(content, list):
397
412
  for item in self.__dict__["_content"]:
@@ -690,6 +705,7 @@ class Container(Box):
690
705
  resolved_node_cache: Optional[Dict[int, "Node"]] = None,
691
706
  ) -> "Node":
692
707
  """A node interpolation is of the form `${foo.bar}`"""
708
+ original_inter_key = inter_key
693
709
  try:
694
710
  root_node, inter_key = self._resolve_key_and_root(inter_key)
695
711
  except ConfigKeyError as exc:
@@ -711,7 +727,19 @@ class Container(Box):
711
727
  ).with_traceback(sys.exc_info()[2])
712
728
 
713
729
  if parent is None or value is None:
714
- raise InterpolationKeyError(f"Interpolation key '{inter_key}' not found")
730
+ msg = f"Interpolation key '{inter_key}' not found"
731
+ if original_inter_key != inter_key:
732
+ try:
733
+ resolved_inter_key = root_node._get_full_key(inter_key)
734
+ except Exception as exc:
735
+ resolved_inter_key = (
736
+ f"<unresolvable due to {type(exc).__name__}: {exc}>"
737
+ )
738
+ msg = (
739
+ f"Interpolation key '{original_inter_key}' not found"
740
+ f" (resolved to '{resolved_inter_key}')"
741
+ )
742
+ raise InterpolationKeyError(msg)
715
743
  else:
716
744
  self._validate_not_dereferencing_to_parent(node=self, target=value)
717
745
  return value
@@ -818,6 +846,7 @@ class Container(Box):
818
846
  def _invalidate_flags_cache(self) -> None:
819
847
  from .dictconfig import DictConfig
820
848
  from .listconfig import ListConfig
849
+ from .tupleconfig import TupleConfig
821
850
 
822
851
  # invalidate subtree cache only if the cache is initialized in this node.
823
852
 
@@ -828,7 +857,7 @@ class Container(Box):
828
857
  if isinstance(content, dict):
829
858
  for value in self.__dict__["_content"].values():
830
859
  value._invalidate_flags_cache()
831
- elif isinstance(self, ListConfig):
860
+ elif isinstance(self, (ListConfig, TupleConfig)):
832
861
  content = self.__dict__["_content"]
833
862
  if isinstance(content, list):
834
863
  for item in self.__dict__["_content"]:
@@ -943,6 +972,7 @@ class UnionNode(Box):
943
972
  ) -> None:
944
973
  from omegaconf.listconfig import ListConfig
945
974
  from omegaconf.omegaconf import _node_wrap
975
+ from omegaconf.tupleconfig import TupleConfig
946
976
 
947
977
  ref_type = self._metadata.ref_type
948
978
  type_hint = self._metadata.type_hint
@@ -956,12 +986,25 @@ class UnionNode(Box):
956
986
  f"Value '$VALUE' is incompatible with type hint '{type_str(type_hint)}'"
957
987
  )
958
988
  self.__dict__["_content"] = value
959
- elif isinstance(value, (list, tuple, ListConfig)):
960
- # Only try List[...] candidates — keeps primitive members out of container
961
- # dispatch and makes ambiguity an error instead of silent first-match.
989
+ elif isinstance(value, (list, tuple, ListConfig, TupleConfig)):
990
+ sequence_candidates = [
991
+ t
992
+ for t in ref_type.__args__
993
+ if is_list_annotation(t) or is_tuple_annotation(t)
994
+ ]
995
+ input_is_tuple = isinstance(value, (tuple, TupleConfig))
996
+ preferred_candidates = [
997
+ t
998
+ for t in sequence_candidates
999
+ if is_tuple_annotation(t) == input_is_tuple
1000
+ ]
1001
+ fallback_candidates = [
1002
+ t for t in sequence_candidates if t not in preferred_candidates
1003
+ ]
962
1004
  self._set_container_value(
963
1005
  value=value,
964
- candidates=[t for t in ref_type.__args__ if is_list_annotation(t)],
1006
+ candidates=preferred_candidates,
1007
+ fallback_candidates=fallback_candidates,
965
1008
  type_hint=type_hint,
966
1009
  )
967
1010
  elif isinstance(value, (dict, Container)):
@@ -994,48 +1037,79 @@ class UnionNode(Box):
994
1037
  value: Any,
995
1038
  candidates: List[Any],
996
1039
  type_hint: Any,
1040
+ fallback_candidates: Optional[List[Any]] = None,
997
1041
  ) -> None:
998
1042
  from omegaconf.listconfig import ListConfig
999
1043
  from omegaconf.omegaconf import _node_wrap
1044
+ from omegaconf.tupleconfig import TupleConfig
1000
1045
 
1001
- from ._utils import get_dict_key_value_types, get_list_element_type
1046
+ from ._utils import (
1047
+ get_dict_key_value_types,
1048
+ get_list_element_type,
1049
+ get_tuple_item_types,
1050
+ normalize_tuple_annotation,
1051
+ )
1002
1052
 
1003
1053
  # For an already-typed container (from typed_list/typed_dict or another
1004
1054
  # structured config), use its element-type metadata to narrow candidates
1005
1055
  # before falling back to content-based validation. This makes empty
1006
1056
  # containers unambiguous when the metadata uniquely identifies one branch.
1007
- if isinstance(value, ListConfig) and value._metadata.element_type is not Any:
1008
- meta_candidates = [
1009
- t
1010
- for t in candidates
1011
- if get_list_element_type(t) == value._metadata.element_type
1012
- ]
1013
- if meta_candidates:
1014
- candidates = meta_candidates
1015
- elif isinstance(value, Container) and not isinstance(value, ListConfig):
1016
- vkey, vval = get_dict_key_value_types(value._metadata.ref_type)
1017
- if vkey is not Any or vval is not Any:
1057
+ def narrow_by_metadata(candidate_types: List[Any]) -> List[Any]:
1058
+ meta_candidates: List[Any] = []
1059
+ if (
1060
+ isinstance(value, ListConfig)
1061
+ and value._metadata.element_type is not Any
1062
+ ):
1018
1063
  meta_candidates = [
1019
- t for t in candidates if get_dict_key_value_types(t) == (vkey, vval)
1064
+ t
1065
+ for t in candidate_types
1066
+ if is_list_annotation(t)
1067
+ and get_list_element_type(t) == value._metadata.element_type
1020
1068
  ]
1021
- if meta_candidates:
1022
- candidates = meta_candidates
1069
+ elif isinstance(value, TupleConfig):
1070
+ value_type = normalize_tuple_annotation(value._metadata.ref_type)
1071
+ value_item_types = get_tuple_item_types(value_type)
1072
+ meta_candidates = [
1073
+ t
1074
+ for t in candidate_types
1075
+ if is_tuple_annotation(t)
1076
+ and get_tuple_item_types(normalize_tuple_annotation(t))
1077
+ == value_item_types
1078
+ ]
1079
+ elif isinstance(value, Container) and not isinstance(
1080
+ value, (ListConfig, TupleConfig)
1081
+ ):
1082
+ vkey, vval = get_dict_key_value_types(value._metadata.ref_type)
1083
+ if vkey is not Any or vval is not Any:
1084
+ meta_candidates = [
1085
+ t
1086
+ for t in candidate_types
1087
+ if is_dict_annotation(t)
1088
+ and get_dict_key_value_types(t) == (vkey, vval)
1089
+ ]
1090
+ return meta_candidates or candidate_types
1091
+
1092
+ def find_matches(candidate_types: List[Any]) -> List[Node]:
1093
+ matches: List[Node] = []
1094
+ for candidate_ref_type in narrow_by_metadata(candidate_types):
1095
+ try:
1096
+ node = _node_wrap(
1097
+ value=value,
1098
+ ref_type=candidate_ref_type,
1099
+ is_optional=False,
1100
+ key=None,
1101
+ parent=self,
1102
+ )
1103
+ matches.append(node)
1104
+ if len(matches) > 1:
1105
+ break
1106
+ except Exception:
1107
+ continue
1108
+ return matches
1023
1109
 
1024
- matches: List[Node] = []
1025
- for candidate_ref_type in candidates:
1026
- try:
1027
- node = _node_wrap(
1028
- value=value,
1029
- ref_type=candidate_ref_type,
1030
- is_optional=False,
1031
- key=None,
1032
- parent=self,
1033
- )
1034
- matches.append(node)
1035
- if len(matches) > 1:
1036
- break # ambiguous — stop constructing further candidates
1037
- except Exception:
1038
- continue
1110
+ matches = find_matches(candidates)
1111
+ if len(matches) == 0 and fallback_candidates:
1112
+ matches = find_matches(fallback_candidates)
1039
1113
 
1040
1114
  if len(matches) == 0:
1041
1115
  raise ValidationError(