def replace(__obj: T, **changes) -> T: # type: ignore from apischema.fields import FIELDS_SET_ATTR, fields_set, set_fields from dataclasses import replace as replace_ result = replace_(__obj, **changes) if hasattr(__obj, FIELDS_SET_ATTR): set_fields(result, *fields_set(__obj), *changes, overwrite=True) return result
def _replace(__obj, **changes): from apischema.fields import FIELDS_SET_ATTR, fields_set, set_fields from dataclasses import replace as replace_, _FIELDS, _FIELD_INITVAR # type: ignore # Fix https://bugs.python.org/issue36470 assert is_dataclass(__obj) for name, field in getattr(__obj, _FIELDS).items(): if field._field_type == _FIELD_INITVAR and name not in changes: # type: ignore if field.default is not MISSING: changes[name] = field.default elif field.default_factory is not MISSING: changes[name] = field.default_factory() result = replace_(__obj, **changes) if hasattr(__obj, FIELDS_SET_ATTR): set_fields(result, *fields_set(__obj), *changes, overwrite=True) return result
def test_fields_set(): with raises(ValueError): fields_set(object()) assert fields_set(Data(0)) == {"without_default"} assert fields_set(Data(without_default=0)) == {"without_default"} assert fields_set(Data(0, 1)) == {"without_default", "with_default"} data = Data(0) data.with_default = 1 assert fields_set(data) == {"without_default", "with_default"} unset_fields(data, "without_default") assert fields_set(data) == {"with_default"} set_fields(data, "with_default_factory") assert fields_set(data) == {"with_default", "with_default_factory"} set_fields(data, "with_default", overwrite=True) assert fields_set(data) == {"with_default"} data.__dict__.pop(FIELDS_SET_ATTR) assert fields_set(data) == { "without_default", "with_default", "with_default_factory", } unset_fields(data, "without_default") assert fields_set(data) == {"with_default", "with_default_factory"} data.__dict__.pop(FIELDS_SET_ATTR) set_fields(data, "with_default") assert fields_set(data) == { "without_default", "with_default", "with_default_factory", } with raises(ValueError): set_fields(data, "not_a_field") assert fields_set(Inherited(0, other=0)) == { "without_default", "with_default", "with_default_factory", "other", } assert fields_set(DecoratedInherited(0, other=0)) == { "without_default", "other", }
def __init__(self, cls: Type, values: Mapping[str, Any]): self.cls = cls self.values = values set_fields(self, *values, overwrite=True)
# This decorator enable the feature @with_fields_set @dataclass class Foo: bar: int baz: Optional[str] = None # Retrieve fields set foo1 = Foo(0, None) assert fields_set(foo1) == {"bar", "baz"} foo2 = Foo(0) assert fields_set(foo2) == {"bar"} # Test fields individually (with autocompletion and refactoring) assert is_set(foo1).baz assert not is_set(foo2).baz # Mark fields as set/unset set_fields(foo2, "baz") assert fields_set(foo2) == {"bar", "baz"} unset_fields(foo2, "baz") assert fields_set(foo2) == {"bar"} set_fields(foo2, "baz", overwrite=True) assert fields_set(foo2) == {"baz"} # Fields modification are taken in account foo2.bar = 0 assert fields_set(foo2) == {"bar", "baz"} # Because deserialization use normal constructor, it works with the feature foo3 = deserialize(Foo, {"bar": 0}) assert fields_set(foo3) == {"bar"}