def test_enum_unstructure(enum, dump_strat, data): """Dumping enums of primitives converts them to their primitives.""" converter = Converter(unstruct_strat=dump_strat) member = data.draw(sampled_from(list(enum.__members__.values()))) assert converter.unstructure(member) == member.value
def test_seq_of_simple_classes_unstructure(cls_and_vals, seq_type): """Dumping a sequence of primitives is a simple copy operation.""" converter = Converter() inputs = seq_type(cl(*vals) for cl, vals in cls_and_vals) outputs = converter.unstructure(inputs) assert type(outputs) == seq_type assert all(type(e) is dict for e in outputs)
def test_mapping_unstructure(map_and_type, dump_strat): """Dumping a mapping of primitives is a simple copy operation.""" converter = Converter(unstruct_strat=dump_strat) mapping = map_and_type[0] dumped = converter.unstructure(mapping) assert dumped == mapping assert dumped is not mapping assert type(dumped) is type(mapping)
def test_enum_unstructure(enum, dump_strat, choice): # type: (EnumMeta, UnstructureStrategy) -> None """Dumping enums of primitives converts them to their primitives.""" converter = Converter(unstruct_strat=dump_strat) member = choice(list(enum.__members__.values())) assert converter.unstructure(member) == member.value
def test_seq_unstructure(seq_and_type, dump_strat): """Dumping a sequence of primitives is a simple copy operation.""" converter = Converter(unstruct_strat=dump_strat) assert converter.unstruct_strat is dump_strat seq = seq_and_type[0] dumped = converter.unstructure(seq) assert dumped == seq if not isinstance(seq, tuple): assert dumped is not seq assert type(dumped) is type(seq)
def test_roundtrip(cl_and_vals): """We dump the class, then we load it.""" converter = Converter() cl, vals = cl_and_vals obj = cl(*vals) dumped = converter.unstructure(obj) loaded = converter.structure(dumped, cl) assert obj == loaded
def test_structure_simple_from_dict(cl_and_vals): """Test structuring non-nested attrs classes dumped with asdict.""" converter = Converter() cl, vals = cl_and_vals obj = cl(*vals) dumped = asdict(obj) loaded = converter.structure(dumped, cl) assert obj == loaded
def test_set_unstructure(set_and_type, dump_strat): """Dumping a set of primitives is a simple copy operation.""" converter = Converter(unstruct_strat=dump_strat) assert converter.unstruct_strat is dump_strat set = set_and_type[0] dumped = converter.unstructure(set) assert dumped == set if set: assert dumped is not set assert type(dumped) is type(set)
def test_unstructure_hooks(cl_and_vals): """ Unstructure hooks work. """ converter = Converter() cl, vals = cl_and_vals inst = cl(*vals) converter.register_unstructure_hook(cl, lambda _: "test") assert converter.unstructure(inst) == "test"
def test_structure_tuple(cl_and_vals): """Test loading from a tuple, by registering the loader.""" converter = Converter() cl, vals = cl_and_vals converter.register_structure_hook(cl, converter.structure_attrs_fromtuple) obj = cl(*vals) dumped = astuple(obj) loaded = converter.structure(dumped, cl) assert obj == loaded
def test_structure_forward_ref(class_with_forward_ref_attr, strat): """ Classes with forward_ref field can be unstructured and structured. """ converter = Converter(unstruct_strat=strat) unstructured_expected = converter.unstructure(class_with_forward_ref_attr) structured = converter.structure(unstructured_expected, C) unstructured_actual = converter.unstructure(structured) assert structured == class_with_forward_ref_attr assert unstructured_actual == unstructured_expected
def test_structure_union(cl_and_vals_a, cl_and_vals_b): """Structuring of automatically-disambiguable unions works.""" converter = Converter() cl_a, vals_a = cl_and_vals_a cl_b, vals_b = cl_and_vals_b a_field_names = {a.name for a in fields(cl_a)} b_field_names = {a.name for a in fields(cl_b)} assume(a_field_names) assume(b_field_names) common_names = a_field_names & b_field_names if len(a_field_names) > len(common_names): obj = cl_a(*vals_a) dumped = asdict(obj) res = converter.structure(dumped, Union[cl_a, cl_b]) assert isinstance(res, cl_a) assert obj == res
def test_structure_simple_from_dict_default(cl_and_vals, data): """Test structuring non-nested attrs classes with default value.""" converter = Converter() cl, vals = cl_and_vals obj = cl(*vals) attrs_with_defaults = [a for a in fields(cl) if a.default is not NOTHING] to_remove = data.draw( lists(elements=sampled_from(attrs_with_defaults), unique=True)) for a in to_remove: if isinstance(a.default, Factory): setattr(obj, a.name, a.default.factory()) else: setattr(obj, a.name, a.default) dumped = asdict(obj) for a in to_remove: del dumped[a.name] assert obj == converter.structure(dumped, cl)
def enrich_unstructured_wildcat(converter: Converter, obj: WC, unstructured_obj_dict: dict) -> dict: wildcat_attrs_names = get_attrs_names(type(obj)) wildcat_nonattrs_dict = { key: converter.unstructure(obj[key]) for key in obj if key not in wildcat_attrs_names } # note that typed entries take absolute precedence over untyped in case of collisions. # these collisions should generally be prevented at runtime by the wildcat # logic that is injected into the type, but if something were to sneak through # we would prefer whatever had been set via the attribute. return {**wildcat_nonattrs_dict, **unstructured_obj_dict}
def test_structure_union_explicit(cl_and_vals_a, cl_and_vals_b): """Structuring of manually-disambiguable unions works.""" converter = Converter() cl_a, vals_a = cl_and_vals_a cl_b, vals_b = cl_and_vals_b def dis(obj, _): return converter.structure(obj, cl_a) converter.register_structure_hook(Union[cl_a, cl_b], dis) inst = cl_a(*vals_a) assert inst == converter.structure(converter.unstructure(inst), Union[cl_a, cl_b])
def test_attrs_astuple_unstructure(nested_class): # type: (Type) -> None """Our dumping should be identical to `attrs`.""" converter = Converter(unstruct_strat=UnstructureStrategy.AS_TUPLE) instance = nested_class[0]() assert converter.unstructure(instance) == astuple(instance)
from marshmallow import fields from cattr.converters import Converter from simple_smartsheet import config from simple_smartsheet import exceptions from simple_smartsheet import utils from simple_smartsheet.types import IndexesType, IndexesKeysType if TYPE_CHECKING: from simple_smartsheet.smartsheet import Smartsheet # noqa: F401 from simple_smartsheet.models.extra import Result logger = logging.getLogger(__name__) converter = Converter() converter.register_structure_hook(datetime, lambda ts, _: ts) converter.register_structure_hook(IndexesKeysType, lambda x, _: x) converter.register_structure_hook(IndexesType, lambda x, _: x) converter.register_structure_hook(Union[float, str, datetime, None], lambda ts, _: ts) class Schema(marshmallow.Schema): class Meta: unknown = utils.get_unknown_field_handling(config.DEBUG) @marshmallow.post_dump def remove_none(self, data): return {key: value for key, value in data.items() if value is not None}
from datetime import date, datetime from typing import List, Dict from typing import Optional, Union import attr import dateutil.parser from cattr.converters import Converter from dateutil import tz converter = Converter() converter.register_unstructure_hook(datetime, lambda dt: dt.isoformat()) converter.register_structure_hook(datetime, lambda ts, _: dateutil.parser.parse(ts)) converter.register_unstructure_hook(date, lambda dt: dt.isoformat()) converter.register_structure_hook(date, lambda ts, _: dateutil.parser.parse(ts)) converter.register_structure_hook( Union[date, datetime], lambda ts, _: dateutil.parser.parse(ts).date() if len(ts) == 10 else dateutil.parser.parse(ts), ) format_version = 2 @attr.s(frozen=True, auto_attribs=True) class Person: name: str party: Optional[str] begin: Optional[date] = None end: Optional[date] = None
from datetime import date, datetime from typing import List, Dict from typing import Optional, Union import attr from cattr.converters import Converter from dateutil import tz converter = Converter() converter.register_unstructure_hook(datetime, lambda dt: dt.isoformat()) converter.register_structure_hook(datetime, lambda ts, _: datetime.fromisoformat(ts)) converter.register_unstructure_hook(date, lambda dt: dt.isoformat()) converter.register_structure_hook(date, lambda ts, _: date.fromisoformat(ts)) converter.register_structure_hook( Optional[Union[date, datetime]], lambda ts, _: date.fromisoformat(ts) if len(ts) == 10 else datetime.fromisoformat(ts), ) format_version = 4 @attr.s(frozen=True, auto_attribs=True) class Person: # Overview page name: str party: Optional[str] begin: Optional[date] = None end: Optional[date] = None original_id: Optional[int] = None
if _is_attrs_class(obj_to_unstructure.__class__): keys_to_strip = _get_names_of_defaulted_nonliteral_attrs( obj_to_unstructure) return { k: v for k, v in unstructured_but_unclean.items() if k not in keys_to_strip } return unstructured_but_unclean class StripAttrsDefaultsOnUnstructurePatch(TypecatsCattrPatch): def unstructure_patch(self, original_handler: ty.Callable, obj_to_unstructure: ty.Any) -> ty.Any: rv = super().unstructure_patch(original_handler, obj_to_unstructure) return _strip_attrs_defaults(rv, obj_to_unstructure) _STRIP_DEFAULTS_CONVERTER = Converter() # create converter __PATCH = StripAttrsDefaultsOnUnstructurePatch( _STRIP_DEFAULTS_CONVERTER) # patch it def get_stripping_converter() -> Converter: return _STRIP_DEFAULTS_CONVERTER def unstruc_strip_defaults(obj: ty.Any) -> ty.Any: """This is the only thing anyone outside needs to worry about""" return _STRIP_DEFAULTS_CONVERTER.unstructure(obj)
def test_attrs_asdict_unstructure(nested_class): """Our dumping should be identical to `attrs`.""" converter = Converter() instance = nested_class[0]() assert converter.unstructure(instance) == asdict(instance)