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

get_attr_data doesn't handle default factory correctly #945

Open
ssnl opened this issue May 25, 2022 · 3 comments · May be fixed by #1134
Open

get_attr_data doesn't handle default factory correctly #945

ssnl opened this issue May 25, 2022 · 3 comments · May be fixed by #1134
Labels
bug Something isn't working

Comments

@ssnl
Copy link

ssnl commented May 25, 2022

If we merge a MISSING field, with an attrs class structured config that uses default factory (e.g., for specifying defaults list in hydra), this expand call

def expand(node: Container) -> None:
rt = node._metadata.ref_type
val: Any
if rt is not Any:
if is_dict_annotation(rt):
val = {}
elif is_list_annotation(rt) or is_tuple_annotation(rt):
val = []
else:
val = rt
elif isinstance(node, DictConfig):
val = {}
else:
assert False
node._set_value(val)

will call node._set_value(attrs_class), which calls get_attr_data(attrs_class).
def get_attr_data(obj: Any, allow_objects: Optional[bool] = None) -> Dict[str, Any]:
from omegaconf.omegaconf import OmegaConf, _maybe_wrap
flags = {"allow_objects": allow_objects} if allow_objects is not None else {}
from omegaconf import MISSING
d = {}
is_type = isinstance(obj, type)
obj_type = obj if is_type else type(obj)
dummy_parent = OmegaConf.create({}, flags=flags)
dummy_parent._metadata.object_type = obj_type
for name, attrib in attr.fields_dict(obj_type).items():
is_optional, type_ = _resolve_optional(attrib.type)
type_ = _resolve_forward(type_, obj.__module__)
if not is_type:
value = getattr(obj, name)
else:
value = attrib.default
if value == attr.NOTHING:
value = MISSING

and since it is given a class, it will use attrs.Attribute's attribute.default value as the values to set attributes. This normally works, but however, if the attribute uses default factory, the attribute.default is an attrs.Factory object! Then OmegaConf errors out.

To Reproduce

image
[skipping long stack frames]
image

@ssnl ssnl added the bug Something isn't working label May 25, 2022
@Jasha10
Copy link
Collaborator

Jasha10 commented May 25, 2022

Thanks for the bug report!

@ssnl
Copy link
Author

ssnl commented May 27, 2022

In a sense this is due to that OmegaConf infers types from values rather than type annotations. I wonder if there can be a way to use type annotations instead (which would also address the structure configs inheritance issue described in facebookresearch/hydra#2227 )

@zoni
Copy link

zoni commented Jun 12, 2022

I ran into this bug as well just now. However, I have found it does work correctly with the stdlib dataclasses. This can serve as a possible workaround for others who run into this, and may aid in tracking down the source of the problem.

For reference, here's a minimalistic dataclasses example that works correctly for which the equivalent attrs version throws the exception described in the original report:

from dataclasses import dataclass, field

@dataclass
class Config:
  a_list_of_strings: list[str] = field(default_factory=list)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants