def test_replace(): obj = WithFieldsSet() assert fields_set(obj) == set() obj2 = std_replace(obj) assert fields_set(obj2) == {"a"} obj3 = replace(obj) assert fields_set(obj3) == set()
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 method( obj: Any, attr_getter=getattr, normal_fields=normal_fields, aggregate_fields=aggregate_fields, ) -> Any: if hasattr(obj, FIELDS_SET_ATTR): fields_set_ = fields_set(obj) normal_fields = [(name, alias, method) for (name, alias, method) in normal_fields if name in fields_set_] aggregate_fields = [(name, method) for (name, method) in aggregate_fields if name in fields_set_] return wrapped_exclude_unset(obj, attr_getter, normal_fields, aggregate_fields)
def method(obj: Any) -> Any: if hasattr(obj, FIELDS_SET_ATTR): fields_set_ = fields_set(obj) new_fields = [ [(name, *_) for name, *_ in fields if name in fields_set_] # type: ignore for fields in [ aggregate_fields, identity_fields, normal_fields, skipped_if_fields, ] ] return wrapped_exclude_unset(obj, *new_fields, lambda: {}) return wrapped_exclude_unset(obj)
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 _serialize( obj: Any, *, conversions: Optional[Conversions] = None, ) -> Any: assert aliaser is not None cls = obj.__class__ if cls in PRIMITIVE_TYPES_SET: return obj if cls in COLLECTION_TYPE_SET: return [_serialize(elt, conversions=conversions) for elt in obj] if cls in MAPPING_TYPE_SET: return { _serialize(key, conversions=conversions): _serialize(value, conversions=conversions) for key, value in obj.items() } target = None if conversions is not None: try: target = conversions[cls] except KeyError: pass conversion = is_conversion(cls, target) if conversion is not None: target, (converter, sub_conversions) = conversion if isinstance(target, DataclassModelWrapper): cls = get_model(target.cls, target.model) else: # TODO Maybe add exclude_unset parameter to serializers return _serialize(converter(obj), conversions=sub_conversions) if is_dataclass(cls): fields, aggregate_fields, serialized_fields = serialization_fields( cls, aliaser) if exclude_unset and hasattr(obj, FIELDS_SET_ATTR): fields_set_ = fields_set(obj) fields = [(name, alias, method) for (name, alias, method) in fields if name in fields_set_] aggregate_fields = [(name, method) for (name, method) in aggregate_fields if name in fields_set_] result = {} # properties before normal fields to avoid overloading a field with property for name, method in aggregate_fields: attr = getattr(obj, name) result.update(method(attr, _serialize)) # type: ignore for name, alias, method in fields: attr = getattr(obj, name) if attr is not Undefined: result[alias] = method(attr, _serialize) # type: ignore for alias, method in serialized_fields: res = method(obj, _serialize) if res is not Undefined: result[alias] = res return result if obj is Undefined: raise Unsupported(cls) if issubclass(cls, Enum): return _serialize(obj.value) if isinstance(obj, PRIMITIVE_TYPES): return obj if isinstance(obj, Mapping): return { _serialize(key): _serialize(value) for key, value in obj.items() } if issubclass(cls, tuple) and hasattr(cls, "_fields"): result = {} for field_name in obj._fields: attr = getattr(obj, field_name) if attr is not Undefined: result[aliaser(field_name)] = attr return result if isinstance(obj, Collection): return [_serialize(elt) for elt in obj] raise Unsupported(cls)
unset_fields, with_fields_set, ) # 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"}