Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OmegaConf.to_object: Instantiate structured configs #502

Merged
merged 90 commits into from
Apr 7, 2021
Merged

OmegaConf.to_object: Instantiate structured configs #502

merged 90 commits into from
Apr 7, 2021

Conversation

Jasha10
Copy link
Collaborator

@Jasha10 Jasha10 commented Jan 30, 2021

Closes #472

The idea is to add an instantiate_structured_configs keyword argument to the OmegaConf.to_container method.
If OmegaConf.to_container(cfg, instantiate_structured_configs=True) is called with this new flag, the returned python object will include instantiated dataclass or attrs instances.

@omry
Copy link
Owner

omry commented Jan 31, 2021

Looking at the code, I believe this does not handle nested structured configs right now.
Also, this means to_container() can actually return an instance now, which is a bit weird:

  1. The same is not great in such a case.
  2. I did not see see the expected type changes demonstrating you are accounting to this at a glance.

We should probably introduce an alias to to_container(), maybe to_object() which will call to_container but will allow Any return type.

@Jasha10
Copy link
Collaborator Author

Jasha10 commented Jan 31, 2021

Looking at the code, I believe this does not handle nested structured configs right now.

I am having success with nested structured configs:

@dataclass
class Inner:
    cost: float

@dataclass
class Outer:
    i: Inner

cfg = OmegaConf.create(Outer(Inner(1.23)))
print(OmegaConf.to_container(cfg, instantiate_structured_configs=True))
# prints Outer(i=Inner(cost=1.23))

It is also working for e.g. structured config as a dict value type.

Also, this means to_container() can actually return an instance now, which is a bit weird:

1. The same is not great in such a case.

2. I did not see see the expected type changes demonstrating you are accounting to this at a glance.

I see what you mean. I'm surprised that mypy did not catch that.

We should probably introduce an alias to to_container(), maybe to_object() which will call to_container but will allow Any return type.

Ok, sounds good. So to_container will keep the old behavior (return python primitives) and to_object will instantiate structured configs?

Copy link
Owner

@omry omry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having success with nested structured configs:

Please add actual tests.
Can you explain why this works?

I also noticed that you are using get_structured_config_data() which is not designed for this (it's used to convert from an sc to a dictconfig) and is actually a rather weird function.
Can you explain what you are using it for exactly, it's not clear to me looking at the code what value it provides you. (it's returning a dictionary where values can be DictConfigs).

Ok, sounds good. So to_container will keep the old behavior (return python primitives) and to_object will instantiate structured configs?

Seems reasonable.
to_container() can still have the new parameter instantiate_structured_configs. (I think a better name for it is instantiate). Doc string should explain it in more detail.

tests/structured_conf/test_structured_config.py Outdated Show resolved Hide resolved
tests/structured_conf/test_structured_config.py Outdated Show resolved Hide resolved
@Jasha10
Copy link
Collaborator Author

Jasha10 commented Feb 3, 2021

Rebased onto master

@Jasha10
Copy link
Collaborator Author

Jasha10 commented Feb 3, 2021

I am having success with nested structured configs:

Please add actual tests.

I've added tests for nested structured config (f02261e).

Can you explain why this works?

It works because BaseContainer._to_content converts leaf nodes first before converting nodes that are higher up in heirarchy.

For example, if you have StructuredConfigOuter.foo == StructuredConfigInner, the _to_content container will first convert StructuredConfigInner to DataClassInner, and will then convert StructuredConfigOuter to DataClassOuter (using DataClassInner to populate the foo attribute of DataClassOuter).

@Jasha10
Copy link
Collaborator Author

Jasha10 commented Feb 3, 2021

I also noticed that you are using get_structured_config_data() which is not designed for this (it's used to convert from an sc to a dictconfig) and is actually a rather weird function.

The get_structured_config_data function is mostly useful in the case where a dataclass subclasses a Dict:

@dataclass
class Str2StrWithField(Dict[str, str]):
    foo: str = "bar"

When the _instantiate_structured_config_impl function instantiates a Str2StrWithField object, it first does

result = Str2StrWithField(foo=...)

to create the object, then it does

result.update(remaining keys that are not "foo")

I'm using the get_structured_config_data function to get a list of attributes for the dataclass, so that we will know which keys should be passed to Str2StrWithField(...) as keyword arguments in the first step, and which keys should be passed to result.update in the second step.

(it's returning a dictionary where values can be DictConfigs).

Hmm...
Do you think I should refactor the useful part of get_structured_config_data into another helper function?

@odelalleau odelalleau self-requested a review March 29, 2021 20:48
Copy link
Collaborator

@odelalleau odelalleau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more comment from me at this point, thanks!

@Jasha10 Jasha10 requested a review from omry March 31, 2021 20:30
omegaconf/basecontainer.py Outdated Show resolved Hide resolved
tests/test_to_container.py Outdated Show resolved Hide resolved
Comment on lines 274 to 275
for k, v in instance_data.items():
if _is_missing_literal(v):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data passed in is derived from the content of self, which is why I am asking it.
It feels redundant.
You have custom code here that is handling things, it can resolve interpolations and deal with containers.

am I missing anything?

@Jasha10 Jasha10 requested a review from omry April 1, 2021 02:48
Comment on lines 274 to 275
for k, v in instance_data.items():
if _is_missing_literal(v):
Copy link
Owner

@omry omry Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a few things there that does not make sense when converting a DictConfig backed by a dataclass to an object:

  1. Support for not resolving interpolations
  2. Support for converting enums to string (doesn't make sense if we are creating an object).

What do you think about this high level approach:

# converts THIS dict config to an object if it's backed by a Structured Config (error otherwise):
DictConfig._to_object()

The implementation will use _to_content(scmode=Instantiate) for any nested containers (ListConfig or DictConfig).
_to_content() can call DictConfig._to_object() if the container it is called on is a DictConfig backed by a Structured Config (and the sc mode is instantiate)

omegaconf/basecontainer.py Outdated Show resolved Hide resolved
tests/test_to_container.py Outdated Show resolved Hide resolved
@Jasha10 Jasha10 requested a review from omry April 1, 2021 23:33
Copy link
Owner

@omry omry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like what I am seeing. A few nits inline.
@odelalleau, can you take a second look? there have been some significant changes.

news/472.feature Outdated Show resolved Hide resolved
docs/source/usage.rst Outdated Show resolved Hide resolved
docs/source/usage.rst Outdated Show resolved Hide resolved
omegaconf/dictconfig.py Outdated Show resolved Hide resolved
Co-authored-by: Omry Yadan <[email protected]>
Copy link
Collaborator

@odelalleau odelalleau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small comments.

Overall looks good to me. I'm not entirely sure we should ignore the resolve flag for structured configs (if someone wants an unresolved dataclass to later convert it back to a DictConfig, why not?), but I don't care much about it so let's go :)

omegaconf/dictconfig.py Outdated Show resolved Hide resolved
node = self._get_node(k)
assert isinstance(node, Node)
node = node._dereference_node(throw_on_resolution_failure=True)
assert node is not None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@omry calling get_node() followed by _dereference_node() is a somewhat common operation and it's a bit annoying right now with the mypy asserts.
Would it make sense to add a get_dereferenced_node() function that would return a Node (not optional) to streamline such code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make sense to me.
I'll do it in another PR :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First step is here: #668

Copy link
Owner

@omry omry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shipit!

@Jasha10 Jasha10 merged commit 0249455 into omry:master Apr 7, 2021
@Jasha10 Jasha10 deleted the instantiate-structured-configs branch April 7, 2021 22:48
@Jasha10 Jasha10 changed the title Instantiate structured configs OmegaConf.to_object: Instantiate structured configs Jun 19, 2024
@Jasha10 Jasha10 changed the title OmegaConf.to_object: Instantiate structured configs OmegaConf.to_object: Instantiate structured configs Jun 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convert Structured Config back to dataclass instance
3 participants