1

OmegaConf allows you to register a custom resolver. Here is an example of resolving a tuple.

def resolve_tuple(*args):
    return tuple(args)

OmegaConf.register_new_resolver("tuple", resolve_tuple)

This can be used to resolve a value in a config file with a structure like ${tuple:1,2} to a tuple (1, 2). Along with hydra.utils.instantiate this can be used to create objects that contain or utilize tuples. For example:

config.yaml

obj:
  tuple: ${tuple:1,2}

test.py

import hydra
import hydra.utils as hu

from omegaconf import OmegaConf


def resolve_tuple(*args):
    return tuple(args)


OmegaConf.register_new_resolver('tuple', resolve_tuple)


@hydra.main(config_path='conf', config_name='config_test')
def main(cfg):
    obj = hu.instantiate(cfg.obj, _convert_='partial')
    print(obj)


if __name__ == '__main__':
    main()

Running this example returns:

$ python test.py
{'tuple': (1, 2)}

However, imagine you had a much more complex config structure. You may want to use interpolation to bring in configs from other files like so.

tuple/base.yaml

tuple: ${tuple:1,2}

config.yaml

defaults:
  - tuple: base
  - _self_

obj:
  tuple: ${tuple}

Running this example you get an error:

$ python test.py
Error executing job with overrides: []
Traceback (most recent call last):
  File "test.py", line 16, in main
    obj = hu.instantiate(cfg.obj, _convert_='partial')
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/instantiate/_instantiate2.py", line 175, in instantiate
    OmegaConf.resolve(config)
omegaconf.errors.UnsupportedValueType: Value 'tuple' is not a supported primitive type

Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace.

The full traceback from hydra is:

Error executing job with overrides: []
Traceback (most recent call last):
  File "test.py", line 21, in <module>
    main()
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/main.py", line 52, in decorated_main
    config_name=config_name,
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/utils.py", line 378, in _run_hydra
    lambda: hydra.run(
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/utils.py", line 214, in run_and_report
    raise ex
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/utils.py", line 211, in run_and_report

    return func()
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/utils.py", line 381, in <lambda>
    overrides=args.overrides,
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/hydra.py", line 111, in run
    _ = ret.return_value
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/core/utils.py", line 233, in return_value
    raise self._return_value
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/core/utils.py", line 160, in run_job
    ret.return_value = task_function(task_cfg)
  File "test.py", line 17, in main
    model = hu.instantiate(cfg.obj, _convert_='partial')
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/hydra/_internal/instantiate/_instantiate2.py", line 175, in instantiate
    OmegaConf.resolve(config)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/omegaconf.py", line 792, in resolve
    omegaconf._impl._resolve(cfg)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/_impl.py", line 40, in _resolve
    _resolve_container_value(cfg, k)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/_impl.py", line 19, in _resolve_container_value
    _resolve(resolved)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/_impl.py", line 40, in _resolve
    _resolve_container_value(cfg, k)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/_impl.py", line 23, in _resolve_container_value
    node._set_value(resolved._value())
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/nodes.py", line 44, in _set_value
    self._val = self.validate_and_convert(value)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/nodes.py", line 57, in validate_and_convert
    return self._validate_and_convert_impl(value)
  File "/Users/me/anaconda3/envs/my_env/lib/python3.7/site-packages/omegaconf/nodes.py", line 134, in _validate_and_convert_impl
    f"Value '{t.__name__}' is not a supported primitive type"
omegaconf.errors.UnsupportedValueType: Value 'tuple' is not a supported primitive type

If you really dig around in the omegaconf code in the trace you will find that there is a flag for the config object allow_objects that is True in the example that passes and None in the example that does not. What is interesting is that in the _instantaite2.py file just before calling Omegaconf.resolve(config) several flags are set, one being allow_objects as True.

Is the intended behavior for these interpolated/resolved values populated from separate files to override this flag? If so, is there some way to ensure that the allow_objects flag is (or remains) true for all resolved and interpolated values?

Grr
  • 15,553
  • 7
  • 65
  • 85

1 Answers1

3

I think there is some confusion because you are using the word tuple for multiple different purposes :)

Here is an example that works for me:

# my_app.py
import hydra
import hydra.utils as hu

from omegaconf import OmegaConf

def resolve_tuple(*args):
    return tuple(args)

OmegaConf.register_new_resolver('as_tuple', resolve_tuple)

@hydra.main(config_path='conf', config_name='config')
def main(cfg):
    obj = hu.instantiate(cfg.obj, _convert_='partial')
    print(obj)

if __name__ == '__main__':
    main()
# conf/config.yaml
defaults:
  - subdir: base
  - _self_

obj:
  a_tuple: ${subdir.my_tuple}
# conf/subdir/base.yaml
my_tuple: ${as_tuple:1,2}
$ python my_app.py  # at the command line:
{'a_tuple': (1, 2)}

The main difference here is that we've got a_tuple: ${subdir.my_tuple} instead of a_tuple: ${my_tuple}.

Notes:

  • Tuples may be supported by OmegaConf as a first-class type at some point in the future. Here's the relevant issue: https://github.com/omry/omegaconf/issues/392
  • The allow_objects flag that you mentioned is undocumented and it's behavior is subject to change.
Jasha
  • 5,507
  • 2
  • 33
  • 44
  • Good to know about the issue. While this works, unfortunately it's still easy to find ways to break this with the same error. Namely if tried to access the `obj` key using interpolation it will break the same way. May just have to stick to using a list in yaml for now. – Grr Oct 21 '21 at 14:51
  • Not sure what you mean about accessing the obj key. But yes, lists are definitely more well-supported at the moment. – Jasha Oct 21 '21 at 16:11