def test_renaming(cl_and_vals, data): converter = Converter() cl, vals = cl_and_vals attrs = fields(cl) to_replace = data.draw(sampled_from(attrs)) u_fn = make_dict_unstructure_fn( cl, converter, **{to_replace.name: override(rename="class")} ) s_fn = make_dict_structure_fn( cl, converter, **{to_replace.name: override(rename="class")} ) converter.register_structure_hook(cl, s_fn) converter.register_unstructure_hook(cl, u_fn) inst = cl(*vals) raw = converter.unstructure(inst) assert "class" in raw new_inst = converter.structure(raw, cl) assert inst == new_inst
def test_nodefs_generated_unstructuring(cl_and_vals): """Test omitting default values on a per-attribute basis.""" converter = Converter() cl, vals = cl_and_vals attr_is_default = False for attr, val in zip(cl.__attrs_attrs__, vals): if attr.default is not NOTHING: fn = make_dict_unstructure_fn( cl, converter, **{attr.name: override(omit_if_default=True)} ) if attr.default == val: attr_is_default = True break else: assume(False) converter.register_unstructure_hook(cl, fn) inst = cl(*vals) res = converter.unstructure(inst) if attr_is_default: assert attr.name not in res
def test_omitting(): converter = Converter() @define class A: a: int b: int = field(init=False) converter.register_unstructure_hook( A, make_dict_unstructure_fn(A, converter, b=override(omit=True))) assert converter.unstructure(A(1)) == {"a": 1}
def test_individual_overrides(cl_and_vals): """ Test omitting default values on a per-class basis, but with individual overrides. """ converter = Converter() cl, vals = cl_and_vals for attr, val in zip(adapted_fields(cl), vals): if attr.default is not NOTHING: break else: assume(False) chosen_name = attr.name converter.register_unstructure_hook( cl, make_dict_unstructure_fn( cl, converter, omit_if_default=True, **{attr.name: override(omit_if_default=False)} ), ) inst = cl(*vals) res = converter.unstructure(inst) assert "Hyp" not in repr(res) assert "Factory" not in repr(res) for attr, val in zip(adapted_fields(cl), vals): if attr.name == chosen_name: assert attr.name in res elif attr.default is not NOTHING: if not isinstance(attr.default, Factory): if val == attr.default: assert attr.name not in res else: assert attr.name in res else: if attr.default.takes_self: if val == attr.default.factory(inst): assert attr.name not in res else: assert attr.name in res else: if val == attr.default.factory(): assert attr.name not in res else: assert attr.name in res
def test_type_overrides(cl_and_vals): """ Type overrides on the GenConverter work. """ converter = Converter(type_overrides={int: override(omit_if_default=True)}) cl, vals = cl_and_vals inst = cl(*vals) unstructured = converter.unstructure(inst) for field, val in zip(fields(cl), vals): if field.type is int: if field.default is not None: if isinstance(field.default, Factory): if not field.default.takes_self and field.default() == val: assert field.name not in unstructured elif field.default == val: assert field.name not in unstructured
def test_individual_overrides(cl_and_vals): """ Test omitting default values on a per-class basis, but with individual overrides. """ converter = Converter() cl, vals = cl_and_vals for attr, val in zip(cl.__attrs_attrs__, vals): if attr.default is not NOTHING: break else: assume(False) chosen = attr converter.register_unstructure_hook( cl, make_dict_unstructure_fn( cl, converter, omit_if_default=True, **{attr.name: override(omit_if_default=False)}), ) inst = cl(*vals) res = converter.unstructure(inst) for attr, val in zip(cl.__attrs_attrs__, vals): if attr is chosen: assert attr.name in res elif attr.default is not NOTHING: if not isinstance(attr.default, Factory): if val == attr.default: assert attr.name not in res else: assert attr.name in res else: if val == attr.default.factory(): assert attr.name not in res else: assert attr.name in res
def attrs_hook_factory(cls: Type[Any], gen_fn: Callable[..., Callable[[Any], Any]], structuring: bool) -> Callable[[Any], Any]: base = get_origin(cls) if base is None: base = cls attribs = fields(base) # PEP563 postponed annotations need resolving as we check Attribute.type below resolve_types(base) kwargs: dict[str, bool | AttributeOverride] = {} if structuring: kwargs["_cattrs_forbid_extra_keys"] = conv.forbid_extra_keys kwargs[ "_cattrs_prefer_attrib_converters"] = conv._prefer_attrib_converters else: kwargs["_cattrs_omit_if_default"] = conv.omit_if_default for a in attribs: if a.type in conv.type_overrides: # cattrs' gen_(un)structure_attrs_fromdict (used by default for attrs # classes that don't have a custom hook registered) check for any # type_overrides (Dict[Type, AttributeOverride]); they allow a custom # converter to omit specific attributes of given type e.g.: # >>> conv = GenConverter(type_overrides={Image: override(omit=True)}) attrib_override = conv.type_overrides[a.type] else: # by default, we omit all Optional attributes (i.e. with None default), # overriding a Converter's global 'omit_if_default' option. Specific # attibutes can still define their own 'omit_if_default' behavior in # the Attribute.metadata dict. attrib_override = override( omit_if_default=a.metadata.get("omit_if_default", a.default is None or None), rename=a.metadata.get( "rename_attr", a.name[1:] if a.name[0] == "_" else None), omit=not a.init, ) kwargs[a.name] = attrib_override return gen_fn(cls, conv, **kwargs)
# This requires that the names of the attributes are renamed during (un-)structuring. # Additionally we only want to unstructure attributes which don't have the default value # (e.g. Layer.default_view_configuration has many attributes which are all optionally). for cls in [ DatasetProperties, MagViewProperties, DatasetViewConfiguration, LayerViewConfiguration, ]: dataset_converter.register_unstructure_hook( cls, make_dict_unstructure_fn( cls, dataset_converter, **{ a.name: override(omit_if_default=True, rename=snake_to_camel_case(a.name)) for a in attr.fields(cls) # pylint: disable=not-an-iterable }, ), ) dataset_converter.register_structure_hook( cls, make_dict_structure_fn( cls, dataset_converter, **{ a.name: override(rename=snake_to_camel_case(a.name)) for a in attr.fields(cls) # pylint: disable=not-an-iterable }, ), )
def structure_adapt_to_camel_case(type): overrides = { a.name: override(rename=to_camel_case(a.name)) for a in fields(type) } return make_dict_structure_fn(type, converter, **overrides)