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()
Exemple #2
0
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
Exemple #3
0
 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)
Exemple #4
0
 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)
Exemple #5
0
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
Exemple #6
0
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",
    }
Exemple #7
0
 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)
Exemple #8
0
    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"}