omegaconf 2.4.0.dev12__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 (162) hide show
  1. {omegaconf-2.4.0.dev12/omegaconf.egg-info → omegaconf-2.4.0.dev13}/PKG-INFO +1 -1
  2. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/__init__.py +2 -0
  3. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/_impl.py +20 -4
  4. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/_utils.py +52 -4
  5. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/base.py +85 -37
  6. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/basecontainer.py +78 -13
  7. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/listconfig.py +23 -5
  8. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/omegaconf.py +117 -30
  9. omegaconf-2.4.0.dev13/omegaconf/tupleconfig.py +522 -0
  10. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/version.py +2 -1
  11. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13/omegaconf.egg-info}/PKG-INFO +1 -1
  12. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/SOURCES.txt +5 -0
  13. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/pyproject.toml +4 -1
  14. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/test_structured_config.py +35 -8
  15. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_base_config.py +1 -1
  16. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_basic_ops_list.py +36 -4
  17. omegaconf-2.4.0.dev13/tests/test_basic_ops_tuple.py +411 -0
  18. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_container_unions.py +86 -2
  19. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_create.py +10 -2
  20. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_errors.py +8 -2
  21. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_merge.py +4 -5
  22. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_nested_containers.py +20 -8
  23. omegaconf-2.4.0.dev13/tests/test_tuple_integration.py +186 -0
  24. omegaconf-2.4.0.dev13/tests/test_tuple_merge.py +178 -0
  25. omegaconf-2.4.0.dev13/tests/test_tuple_structured.py +205 -0
  26. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_utils.py +4 -1
  27. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/LICENSE +0 -0
  28. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/MANIFEST.in +0 -0
  29. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/README.md +0 -0
  30. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/build_helpers/__init__.py +0 -0
  31. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/build_helpers/bin/antlr-4.11.1-complete.jar +0 -0
  32. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/build_helpers/build_helpers.py +0 -0
  33. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/build_helpers/get_vendored.py +0 -0
  34. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/build_helpers/test_helpers.py +0 -0
  35. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/_yaml.py +0 -0
  36. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/dictconfig.py +0 -0
  37. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/errors.py +0 -0
  38. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/OmegaConfGrammarLexer.g4 +0 -0
  39. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/OmegaConfGrammarParser.g4 +0 -0
  40. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/__init__.py +0 -0
  41. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarLexer.py +0 -0
  42. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParser.py +0 -0
  43. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParserListener.py +0 -0
  44. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/OmegaConfGrammarParserVisitor.py +0 -0
  45. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar/gen/__init__.py +0 -0
  46. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar_parser.py +0 -0
  47. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/grammar_visitor.py +0 -0
  48. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/nodes.py +0 -0
  49. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/py.typed +0 -0
  50. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/__init__.py +0 -0
  51. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/oc/__init__.py +0 -0
  52. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/resolvers/oc/dict.py +0 -0
  53. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/typing.py +0 -0
  54. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/__init__.py +0 -0
  55. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/BufferedTokenStream.py +0 -0
  56. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/CommonTokenFactory.py +0 -0
  57. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/CommonTokenStream.py +0 -0
  58. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/FileStream.py +0 -0
  59. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/InputStream.py +0 -0
  60. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/IntervalSet.py +0 -0
  61. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/LL1Analyzer.py +0 -0
  62. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Lexer.py +0 -0
  63. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ListTokenSource.py +0 -0
  64. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Parser.py +0 -0
  65. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ParserInterpreter.py +0 -0
  66. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/ParserRuleContext.py +0 -0
  67. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/PredictionContext.py +0 -0
  68. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Recognizer.py +0 -0
  69. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/RuleContext.py +0 -0
  70. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/StdinStream.py +0 -0
  71. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Token.py +0 -0
  72. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/TokenStreamRewriter.py +0 -0
  73. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/Utils.py +0 -0
  74. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/__init__.py +0 -0
  75. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/_pygrun.py +0 -0
  76. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATN.py +0 -0
  77. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNConfig.py +0 -0
  78. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNConfigSet.py +0 -0
  79. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNDeserializationOptions.py +0 -0
  80. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNDeserializer.py +0 -0
  81. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNSimulator.py +0 -0
  82. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNState.py +0 -0
  83. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ATNType.py +0 -0
  84. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerATNSimulator.py +0 -0
  85. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerAction.py +0 -0
  86. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/LexerActionExecutor.py +0 -0
  87. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/ParserATNSimulator.py +0 -0
  88. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/PredictionMode.py +0 -0
  89. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/SemanticContext.py +0 -0
  90. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/Transition.py +0 -0
  91. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/atn/__init__.py +0 -0
  92. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFA.py +0 -0
  93. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFASerializer.py +0 -0
  94. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/DFAState.py +0 -0
  95. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/dfa/__init__.py +0 -0
  96. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/DiagnosticErrorListener.py +0 -0
  97. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/ErrorListener.py +0 -0
  98. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/ErrorStrategy.py +0 -0
  99. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/Errors.py +0 -0
  100. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/error/__init__.py +0 -0
  101. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Chunk.py +0 -0
  102. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreeMatch.py +0 -0
  103. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreePattern.py +0 -0
  104. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/ParseTreePatternMatcher.py +0 -0
  105. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/RuleTagToken.py +0 -0
  106. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/TokenTagToken.py +0 -0
  107. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Tree.py +0 -0
  108. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/Trees.py +0 -0
  109. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/tree/__init__.py +0 -0
  110. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/XPath.py +0 -0
  111. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/XPathLexer.py +0 -0
  112. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf/vendor/antlr4/xpath/__init__.py +0 -0
  113. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/dependency_links.txt +0 -0
  114. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/requires.txt +0 -0
  115. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/omegaconf.egg-info/top_level.txt +0 -0
  116. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/requirements/base.txt +0 -0
  117. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/requirements/dev.txt +0 -0
  118. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/requirements/docs.txt +0 -0
  119. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/setup.cfg +0 -0
  120. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/setup.py +0 -0
  121. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/__init__.py +0 -0
  122. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/conftest.py +0 -0
  123. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/data/2.0.6.pickle +0 -0
  124. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/data/2.1.0.rc1.pickle +0 -0
  125. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/data/load.py +0 -0
  126. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/data/save.py +0 -0
  127. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/examples/__init__.py +0 -0
  128. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/examples/dataclass_postponed_annotations.py +0 -0
  129. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/examples/test_dataclass_example.py +0 -0
  130. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/examples/test_postponed_annotations.py +0 -0
  131. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/__init__.py +0 -0
  132. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/__init__.py +0 -0
  133. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_create.py +0 -0
  134. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_decode.py +0 -0
  135. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_deprecated.py +0 -0
  136. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_dict.py +0 -0
  137. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_env.py +0 -0
  138. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/built_in_resolvers/test_oc_select.py +0 -0
  139. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/test_custom_resolvers.py +0 -0
  140. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/interpolation/test_interpolation.py +0 -0
  141. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/__init__.py +0 -0
  142. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/__init__.py +0 -0
  143. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/attr_classes.py +0 -0
  144. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/dataclasses.py +0 -0
  145. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/data/dataclasses_pre_311.py +0 -0
  146. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/structured_conf/test_structured_basic.py +0 -0
  147. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_basic_ops_dict.py +0 -0
  148. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_compare_dictconfig_vs_dict.py +0 -0
  149. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_config_eq.py +0 -0
  150. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_get_full_key.py +0 -0
  151. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_grammar.py +0 -0
  152. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_matrix.py +0 -0
  153. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_nodes.py +0 -0
  154. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_omegaconf.py +0 -0
  155. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_readonly.py +0 -0
  156. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_select.py +0 -0
  157. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_serialization.py +0 -0
  158. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_struct.py +0 -0
  159. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_to_container.py +0 -0
  160. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_to_yaml.py +0 -0
  161. {omegaconf-2.4.0.dev12 → omegaconf-2.4.0.dev13}/tests/test_unions.py +0 -0
  162. {omegaconf-2.4.0.dev12 → 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.dev12
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
@@ -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
 
@@ -742,12 +753,45 @@ def get_list_element_type(ref_type: Optional[Type[Any]]) -> Any:
742
753
 
743
754
  def get_tuple_item_types(ref_type: Type[Any]) -> Tuple[Any, ...]:
744
755
  args = getattr(ref_type, "__args__", None)
745
- if args in (None, ()):
756
+ if ref_type in (tuple, Tuple) or args is None:
746
757
  args = (Any, ...)
758
+ elif args == ((),):
759
+ args = ()
747
760
  assert isinstance(args, tuple)
748
761
  return args
749
762
 
750
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
+
751
795
  def get_dict_key_value_types(ref_type: Any) -> Tuple[Any, Any]:
752
796
  args = getattr(ref_type, "__args__", None)
753
797
  if args is None:
@@ -1043,14 +1087,14 @@ def _ensure_container(target: Any, flags: Optional[Dict[str, bool]] = None) -> A
1043
1087
  from omegaconf import OmegaConf
1044
1088
 
1045
1089
  if is_primitive_container(target):
1046
- assert isinstance(target, (list, dict))
1090
+ assert isinstance(target, (list, tuple, dict))
1047
1091
  target = OmegaConf.create(target, flags=flags)
1048
1092
  elif is_structured_config(target):
1049
1093
  target = OmegaConf.structured(target, flags=flags)
1050
1094
  elif not OmegaConf.is_config(target):
1051
1095
  raise ValueError(
1052
1096
  "Invalid input. Supports one of "
1053
- + "[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]"
1054
1098
  )
1055
1099
 
1056
1100
  return target
@@ -1083,7 +1127,11 @@ def is_generic_dict(type_: Any) -> bool:
1083
1127
 
1084
1128
 
1085
1129
  def is_container_annotation(type_: Any) -> bool:
1086
- 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
+ )
1087
1135
 
1088
1136
 
1089
1137
  # Characters recognised after a backslash in key paths and dotlist entries.
@@ -30,6 +30,7 @@ from ._utils import (
30
30
  get_value_kind,
31
31
  is_dict_annotation,
32
32
  is_list_annotation,
33
+ is_tuple_annotation,
33
34
  is_union_annotation,
34
35
  is_valid_value_annotation,
35
36
  split_key,
@@ -377,7 +378,7 @@ class Node(ABC):
377
378
  class Box(Node):
378
379
  """
379
380
  Base class for nodes that can contain other nodes.
380
- Concrete subclasses include DictConfig, ListConfig, and UnionNode.
381
+ Concrete subclasses include DictConfig, ListConfig, TupleConfig, and UnionNode.
381
382
  """
382
383
 
383
384
  _content: Any
@@ -393,6 +394,7 @@ class Box(Node):
393
394
  def _re_parent(self) -> None:
394
395
  from .dictconfig import DictConfig
395
396
  from .listconfig import ListConfig
397
+ from .tupleconfig import TupleConfig
396
398
 
397
399
  # update parents of first level Config nodes to self
398
400
 
@@ -404,7 +406,7 @@ class Box(Node):
404
406
  value._set_parent(self)
405
407
  if isinstance(value, Box):
406
408
  value._re_parent()
407
- elif isinstance(self, ListConfig):
409
+ elif isinstance(self, (ListConfig, TupleConfig)):
408
410
  content = self.__dict__["_content"]
409
411
  if isinstance(content, list):
410
412
  for item in self.__dict__["_content"]:
@@ -844,6 +846,7 @@ class Container(Box):
844
846
  def _invalidate_flags_cache(self) -> None:
845
847
  from .dictconfig import DictConfig
846
848
  from .listconfig import ListConfig
849
+ from .tupleconfig import TupleConfig
847
850
 
848
851
  # invalidate subtree cache only if the cache is initialized in this node.
849
852
 
@@ -854,7 +857,7 @@ class Container(Box):
854
857
  if isinstance(content, dict):
855
858
  for value in self.__dict__["_content"].values():
856
859
  value._invalidate_flags_cache()
857
- elif isinstance(self, ListConfig):
860
+ elif isinstance(self, (ListConfig, TupleConfig)):
858
861
  content = self.__dict__["_content"]
859
862
  if isinstance(content, list):
860
863
  for item in self.__dict__["_content"]:
@@ -969,6 +972,7 @@ class UnionNode(Box):
969
972
  ) -> None:
970
973
  from omegaconf.listconfig import ListConfig
971
974
  from omegaconf.omegaconf import _node_wrap
975
+ from omegaconf.tupleconfig import TupleConfig
972
976
 
973
977
  ref_type = self._metadata.ref_type
974
978
  type_hint = self._metadata.type_hint
@@ -982,12 +986,25 @@ class UnionNode(Box):
982
986
  f"Value '$VALUE' is incompatible with type hint '{type_str(type_hint)}'"
983
987
  )
984
988
  self.__dict__["_content"] = value
985
- elif isinstance(value, (list, tuple, ListConfig)):
986
- # Only try List[...] candidates — keeps primitive members out of container
987
- # 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
+ ]
988
1004
  self._set_container_value(
989
1005
  value=value,
990
- candidates=[t for t in ref_type.__args__ if is_list_annotation(t)],
1006
+ candidates=preferred_candidates,
1007
+ fallback_candidates=fallback_candidates,
991
1008
  type_hint=type_hint,
992
1009
  )
993
1010
  elif isinstance(value, (dict, Container)):
@@ -1020,48 +1037,79 @@ class UnionNode(Box):
1020
1037
  value: Any,
1021
1038
  candidates: List[Any],
1022
1039
  type_hint: Any,
1040
+ fallback_candidates: Optional[List[Any]] = None,
1023
1041
  ) -> None:
1024
1042
  from omegaconf.listconfig import ListConfig
1025
1043
  from omegaconf.omegaconf import _node_wrap
1044
+ from omegaconf.tupleconfig import TupleConfig
1026
1045
 
1027
- 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
+ )
1028
1052
 
1029
1053
  # For an already-typed container (from typed_list/typed_dict or another
1030
1054
  # structured config), use its element-type metadata to narrow candidates
1031
1055
  # before falling back to content-based validation. This makes empty
1032
1056
  # containers unambiguous when the metadata uniquely identifies one branch.
1033
- if isinstance(value, ListConfig) and value._metadata.element_type is not Any:
1034
- meta_candidates = [
1035
- t
1036
- for t in candidates
1037
- if get_list_element_type(t) == value._metadata.element_type
1038
- ]
1039
- if meta_candidates:
1040
- candidates = meta_candidates
1041
- elif isinstance(value, Container) and not isinstance(value, ListConfig):
1042
- vkey, vval = get_dict_key_value_types(value._metadata.ref_type)
1043
- 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
+ ):
1063
+ meta_candidates = [
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
1068
+ ]
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)
1044
1072
  meta_candidates = [
1045
- t for t in candidates if get_dict_key_value_types(t) == (vkey, vval)
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
1046
1078
  ]
1047
- if meta_candidates:
1048
- candidates = meta_candidates
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
1049
1109
 
1050
- matches: List[Node] = []
1051
- for candidate_ref_type in candidates:
1052
- try:
1053
- node = _node_wrap(
1054
- value=value,
1055
- ref_type=candidate_ref_type,
1056
- is_optional=False,
1057
- key=None,
1058
- parent=self,
1059
- )
1060
- matches.append(node)
1061
- if len(matches) > 1:
1062
- break # ambiguous — stop constructing further candidates
1063
- except Exception:
1064
- continue
1110
+ matches = find_matches(candidates)
1111
+ if len(matches) == 0 and fallback_candidates:
1112
+ matches = find_matches(fallback_candidates)
1065
1113
 
1066
1114
  if len(matches) == 0:
1067
1115
  raise ValidationError(
@@ -131,6 +131,8 @@ class BaseContainer(Container, ABC):
131
131
  dict_copy["_metadata"].ref_type = Dict
132
132
  elif is_list_annotation(ref_type):
133
133
  dict_copy["_metadata"].ref_type = List
134
+ elif is_tuple_annotation(ref_type):
135
+ pass
134
136
  else:
135
137
  assert False
136
138
  return dict_copy
@@ -164,6 +166,8 @@ class BaseContainer(Container, ABC):
164
166
  ]
165
167
  elif is_generic_list(ref_type):
166
168
  state_dict["_metadata"].ref_type = List[element_type] # type: ignore
169
+ elif is_tuple_annotation(ref_type):
170
+ pass
167
171
  else:
168
172
  assert False
169
173
 
@@ -253,8 +257,8 @@ class BaseContainer(Container, ABC):
253
257
  enum_to_str: bool = False,
254
258
  structured_config_mode: SCMode = SCMode.DICT,
255
259
  resolved_node_cache: Optional[Dict[int, Node]] = None,
256
- ) -> Union[None, Any, str, Dict[DictKeyType, Any], List[Any]]:
257
- from omegaconf import MISSING, DictConfig, ListConfig
260
+ ) -> Union[None, Any, str, Dict[DictKeyType, Any], List[Any], Tuple[Any, ...]]:
261
+ from omegaconf import MISSING, DictConfig, ListConfig, TupleConfig
258
262
 
259
263
  if resolve and resolved_node_cache is None:
260
264
  resolved_node_cache = {}
@@ -352,6 +356,8 @@ class BaseContainer(Container, ABC):
352
356
  retlist.append(item)
353
357
 
354
358
  return retlist
359
+ elif isinstance(conf, TupleConfig):
360
+ return tuple(get_node_value(index) for index in range(len(conf)))
355
361
  assert False
356
362
 
357
363
  @staticmethod
@@ -362,7 +368,7 @@ class BaseContainer(Container, ABC):
362
368
  _allow_readonly_target: bool = False,
363
369
  ) -> None:
364
370
  """merge src into dest and return a new copy, does not modified input"""
365
- from omegaconf import AnyNode, DictConfig, ValueNode
371
+ from omegaconf import AnyNode, DictConfig, ListConfig, TupleConfig, ValueNode
366
372
 
367
373
  assert isinstance(dest, DictConfig)
368
374
  assert isinstance(src, DictConfig)
@@ -440,13 +446,18 @@ class BaseContainer(Container, ABC):
440
446
 
441
447
  if (
442
448
  isinstance(dest_node, Container)
449
+ and not isinstance(dest_node, TupleConfig)
443
450
  and dest_node._is_none()
444
451
  and not src_node_missing
445
452
  and not _is_none(src_node, resolve=True)
446
453
  ):
447
454
  expand(dest_node)
448
455
 
449
- if dest_node is not None and dest_node._is_interpolation():
456
+ if (
457
+ dest_node is not None
458
+ and not isinstance(dest_node, TupleConfig)
459
+ and dest_node._is_interpolation()
460
+ ):
450
461
  target_node = dest_node._maybe_dereference_node()
451
462
  if isinstance(target_node, Container):
452
463
  dest[key] = target_node
@@ -462,7 +473,11 @@ class BaseContainer(Container, ABC):
462
473
 
463
474
  if dest_node is not None:
464
475
  if isinstance(dest_node, BaseContainer):
465
- if isinstance(src_node, BaseContainer):
476
+ if isinstance(dest_node, TupleConfig) and isinstance(
477
+ src_node, (ListConfig, TupleConfig)
478
+ ):
479
+ BaseContainer._tuple_merge(dest_node, src_node)
480
+ elif isinstance(src_node, BaseContainer):
466
481
  dest_node._merge_with(
467
482
  src_node,
468
483
  list_merge_mode=list_merge_mode,
@@ -562,6 +577,21 @@ class BaseContainer(Container, ABC):
562
577
  if value is not None:
563
578
  dest._set_flag(flag, value)
564
579
 
580
+ @staticmethod
581
+ def _tuple_merge(dest: Any, src: Any) -> None:
582
+ from omegaconf import ListConfig, TupleConfig
583
+
584
+ assert isinstance(dest, TupleConfig)
585
+ assert isinstance(src, (ListConfig, TupleConfig))
586
+ if not src._is_missing():
587
+ dest._set_value(src)
588
+
589
+ flags = src._metadata.flags
590
+ assert flags is not None
591
+ for flag, value in flags.items():
592
+ if value is not None:
593
+ dest._set_flag(flag, value)
594
+
565
595
  def merge_with(
566
596
  self,
567
597
  *others: Union[
@@ -587,6 +617,7 @@ class BaseContainer(Container, ABC):
587
617
  ) -> None:
588
618
  from .dictconfig import DictConfig
589
619
  from .listconfig import ListConfig
620
+ from .tupleconfig import TupleConfig
590
621
 
591
622
  """merge a list of other Config objects into this one, overriding as needed"""
592
623
  for other in others:
@@ -615,14 +646,26 @@ class BaseContainer(Container, ABC):
615
646
  list_merge_mode=list_merge_mode,
616
647
  _allow_readonly_target=_allow_readonly_target,
617
648
  )
618
- elif isinstance(self, ListConfig) and isinstance(other, ListConfig):
649
+ elif isinstance(self, ListConfig) and isinstance(
650
+ other, (ListConfig, TupleConfig)
651
+ ):
652
+ if isinstance(other, TupleConfig):
653
+ other = ListConfig(content=other)
619
654
  BaseContainer._list_merge(
620
655
  self,
621
656
  other,
622
657
  list_merge_mode=list_merge_mode,
623
658
  )
659
+ elif isinstance(self, TupleConfig) and isinstance(
660
+ other, (ListConfig, TupleConfig)
661
+ ):
662
+ if not _allow_readonly_target:
663
+ raise ConfigTypeError(
664
+ "Cannot merge into a TupleConfig in-place"
665
+ )
666
+ BaseContainer._tuple_merge(self, other)
624
667
  else:
625
- raise TypeError("Cannot merge DictConfig with ListConfig")
668
+ raise TypeError("Cannot merge incompatible container types")
626
669
  finally:
627
670
  if readonly_overridden:
628
671
  self._set_flag("readonly", prev_readonly)
@@ -829,6 +872,9 @@ class BaseContainer(Container, ABC):
829
872
  def _get_full_key(self, key: Union[DictKeyType, int, slice, None]) -> str:
830
873
  from .listconfig import ListConfig
831
874
  from .omegaconf import _select_one
875
+ from .tupleconfig import TupleConfig
876
+
877
+ sequence_types = (ListConfig, TupleConfig)
832
878
 
833
879
  if not isinstance(key, (int, str, Enum, float, bool, slice, bytes, type(None))):
834
880
  return ""
@@ -857,9 +903,9 @@ class BaseContainer(Container, ABC):
857
903
 
858
904
  assert isinstance(key, str)
859
905
 
860
- if issubclass(parent_type, ListConfig):
906
+ if issubclass(parent_type, sequence_types):
861
907
  if full_key != "":
862
- if issubclass(cur_type, ListConfig):
908
+ if issubclass(cur_type, sequence_types):
863
909
  full_key = f"[{key}]{full_key}"
864
910
  else:
865
911
  full_key = f"[{key}].{full_key}"
@@ -869,7 +915,7 @@ class BaseContainer(Container, ABC):
869
915
  if full_key == "":
870
916
  full_key = key
871
917
  else:
872
- if issubclass(cur_type, ListConfig):
918
+ if issubclass(cur_type, sequence_types):
873
919
  full_key = f"{key}{full_key}"
874
920
  else:
875
921
  full_key = f"{key}.{full_key}"
@@ -952,9 +998,14 @@ def _update_types(node: Node, ref_type: Any, object_type: Optional[type]) -> Non
952
998
 
953
999
  def _deep_update_type_hint(node: Node, type_hint: Any) -> None:
954
1000
  """Ensure node is compatible with type_hint, mutating if necessary."""
955
- from omegaconf import DictConfig, ListConfig, OmegaConf
1001
+ from omegaconf import DictConfig, ListConfig, OmegaConf, TupleConfig
956
1002
 
957
- from ._utils import get_dict_key_value_types, get_list_element_type
1003
+ from ._utils import (
1004
+ get_dict_key_value_types,
1005
+ get_list_element_type,
1006
+ get_tuple_item_types,
1007
+ is_variadic_tuple_annotation,
1008
+ )
958
1009
 
959
1010
  if type_hint is Any:
960
1011
  return
@@ -986,6 +1037,18 @@ def _deep_update_type_hint(node: Node, type_hint: Any) -> None:
986
1037
  for i in range(len(node)):
987
1038
  _deep_update_subnode(node, i, new_element_type)
988
1039
 
1040
+ if is_tuple_annotation(new_ref_type) and isinstance(node, TupleConfig):
1041
+ item_types = get_tuple_item_types(new_ref_type)
1042
+ variadic = is_variadic_tuple_annotation(new_ref_type)
1043
+ if not variadic and len(node) != len(item_types):
1044
+ raise ValidationError(
1045
+ f"TupleConfig length {len(node)} does not match type hint length {len(item_types)}"
1046
+ )
1047
+ if not _is_special(node):
1048
+ for index in range(len(node)):
1049
+ item_type = item_types[0] if variadic else item_types[index]
1050
+ _deep_update_subnode(node, index, item_type)
1051
+
989
1052
  if is_dict_annotation(new_ref_type) and isinstance(node, DictConfig):
990
1053
  new_key_type, new_element_type = get_dict_key_value_types(new_ref_type)
991
1054
  node._metadata.key_type = new_key_type
@@ -1028,7 +1091,7 @@ def _deep_update_subnode(node: BaseContainer, key: Any, value_type_hint: Any) ->
1028
1091
 
1029
1092
  def _shallow_validate_type_hint(node: Node, type_hint: Any) -> None:
1030
1093
  """Error if node's type, content and metadata are not compatible with type_hint."""
1031
- from omegaconf import DictConfig, ListConfig, UnionNode, ValueNode
1094
+ from omegaconf import DictConfig, ListConfig, TupleConfig, UnionNode, ValueNode
1032
1095
 
1033
1096
  is_optional, ref_type = _resolve_optional(type_hint)
1034
1097
 
@@ -1067,6 +1130,8 @@ def _shallow_validate_type_hint(node: Node, type_hint: Any) -> None:
1067
1130
  return
1068
1131
  elif is_list_annotation(ref_type) and isinstance(node, ListConfig):
1069
1132
  return
1133
+ elif is_tuple_annotation(ref_type) and isinstance(node, TupleConfig):
1134
+ return
1070
1135
  else:
1071
1136
  if isinstance(node, ValueNode):
1072
1137
  value = node._value()
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  from typing import (
3
+ TYPE_CHECKING,
3
4
  Any,
4
5
  Callable,
5
6
  Dict,
@@ -13,6 +14,9 @@ from typing import (
13
14
  Union,
14
15
  )
15
16
 
17
+ if TYPE_CHECKING:
18
+ from .tupleconfig import TupleConfig
19
+
16
20
  from ._utils import (
17
21
  ValueKind,
18
22
  _get_value,
@@ -44,7 +48,9 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
44
48
 
45
49
  def __init__(
46
50
  self,
47
- content: Union[List[Any], Tuple[Any, ...], "ListConfig", str, None],
51
+ content: Union[
52
+ List[Any], Tuple[Any, ...], "ListConfig", "TupleConfig", str, None
53
+ ],
48
54
  key: Any = None,
49
55
  parent: Optional[Box] = None,
50
56
  element_type: Union[Type[Any], Any] = Any,
@@ -322,7 +328,13 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
322
328
  assert False
323
329
 
324
330
  def extend(self, lst: Iterable[Any]) -> None:
325
- assert isinstance(lst, (tuple, list, ListConfig))
331
+ from omegaconf.tupleconfig import TupleConfig
332
+
333
+ if not isinstance(lst, (tuple, list, ListConfig, TupleConfig)):
334
+ raise TypeError(
335
+ "ListConfig.extend() expected a list, tuple, ListConfig, "
336
+ f"or TupleConfig, got {type(lst).__name__}"
337
+ )
326
338
  for x in lst:
327
339
  self.append(x)
328
340
 
@@ -604,6 +616,7 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
604
616
  self, value: Any, flags: Optional[Dict[str, bool]] = None
605
617
  ) -> None:
606
618
  from omegaconf import MISSING, flag_override
619
+ from omegaconf.tupleconfig import TupleConfig
607
620
 
608
621
  if flags is None:
609
622
  flags = {}
@@ -623,13 +636,18 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
623
636
  self.__dict__["_content"] = value
624
637
  self._metadata.object_type = None
625
638
  else:
626
- if not (is_primitive_list(value) or isinstance(value, ListConfig)):
639
+ if not (
640
+ is_primitive_list(value) or isinstance(value, (ListConfig, TupleConfig))
641
+ ):
627
642
  type_ = type(value)
628
- msg = f"Invalid value assigned: {type_.__name__} is not a ListConfig, list or tuple."
643
+ msg = (
644
+ f"Invalid value assigned: {type_.__name__} is not a "
645
+ "ListConfig, TupleConfig, list, or tuple."
646
+ )
629
647
  raise ValidationError(msg)
630
648
 
631
649
  self.__dict__["_content"] = []
632
- if isinstance(value, ListConfig):
650
+ if isinstance(value, (ListConfig, TupleConfig)):
633
651
  self._metadata.flags = copy.deepcopy(flags)
634
652
  # disable struct and readonly for the construction phase
635
653
  # retaining other flags like allow_objects. The real flags are restored at the end of this function