Improve handling of init=False or _fields, fix crashes

- Don't dump init=False fields
- Don't crash when loading init=False fields
- Pass field to __init__(field), if internally named _field

Fixes crash when playing PostTriggerConfig from GUI.
pull/357/head
nyanpasu64 2019-03-05 10:25:29 -08:00
rodzic a289349d9c
commit e21aeef072
2 zmienionych plików z 48 dodań i 10 usunięć

Wyświetl plik

@ -153,11 +153,15 @@ class DumpableAttrs:
cls = type(self)
for field in attr.fields(cls):
# Skip deprecated fields with leading underscores.
# They have already been baked into other config fields.
"""
Skip fields which cannot be passed into __init__().
- init=False
- leading underscores: deprecated fields.
- They have already been baked into other config fields.
- Even if you want to keep them, __init__(field) removes underscore.
"""
name = field.name
if name[0] == "_":
if name[0] == "_" or not field.init:
continue
value = getattr(self, name)
@ -184,12 +188,16 @@ class DumpableAttrs:
""" Redirect `Alias(key)=value` to `key=value`.
Then call the dataclass constructor (to validate parameters). """
self_name = obj_name(self)
field_names = attr.fields_dict(type(self)).keys()
cls = type(self)
cls_name = cls.__name__
fields = attr.fields_dict(cls)
# All names which can be passed into __init__()
field_names = {name.lstrip("_") for name, field in fields.items() if field.init}
new_state = {}
for key, value in dict(state).items():
class_var = getattr(self, key, None)
class_var = getattr(cls, key, None)
if class_var is Ignored:
pass
@ -198,21 +206,21 @@ class DumpableAttrs:
target = class_var.key
if target in state:
raise CorrError(
f"{self_name} received both Alias {key} and "
f"{cls_name} received both Alias {key} and "
f"equivalent {target}"
)
new_state[target] = value
elif key not in field_names:
warnings.warn(
f'Unrecognized field "{key}" in !{self_name}, ignoring', CorrWarning
f'Unrecognized field "{key}" in !{cls_name}, ignoring', CorrWarning
)
else:
new_state[key] = value
del state
obj = type(self)(**new_state)
obj = cls(**new_state)
self.__dict__ = obj.__dict__

Wyświetl plik

@ -291,6 +291,36 @@ foo: 0
assert yaml.load(s) == Foo(99)
# Test handling of _prefix fields, or init=False
@pytest.mark.filterwarnings("ignore:")
def test_skip_dump_load():
"""Ensure _fields or init=False are not dumped,
and don't crash on loading.
"""
class Foo(DumpableAttrs):
_underscore: int
init_false: int = attr.ib(init=False)
def __attrs_post_init__(self):
self.init_false = 1
# Ensure fields are not dumped.
foo = Foo(underscore=1)
assert yaml.dump(foo).strip() == "!Foo {}"
# Ensure init=False fields don't crash on loading.
evil = """\
!Foo
underscore: 1
_underscore: 2
init_false: 3
"""
assert yaml.load(evil)._underscore == 1
# Test always_dump validation.