PKzN dataclass_structor/__init__.py"""Typed object structure/destructure.""" from .structure import structure from .unstructure import unstructure __version__ = "0.0.6" __all__ = [str(structure), str(unstructure)] PKjgN()n66dataclass_structor/structure.pyimport decimal import datetime import enum import uuid from typing import ( get_type_hints, Any, Callable, Dict, List, Iterable, Optional, Set, Tuple, Type, TypeVar, Union, ) T = TypeVar("T") # pylint: disable=invalid-name def structure(value: Any, goal_type: Any) -> Any: """Returns object given a value and type signature to be coerced into. :param value: A dict or list composed of primitive type (str, int, float) or a primitive type. :param goal_type: A type that you would like cast `value` into. Usage:: >>> import datetime >>> import dataclass_structor >>> dataclass_structor.structure('2018-10-02', datetime.date) datetime.datetime(2018, 10, 2) """ if value is None: return value if hasattr(goal_type, "__origin__") and goal_type.__origin__ is Union: return _structure_union(value, goal_type.__args__) return _structure_value(value, goal_type) _STRUCTURE_UNION_TYPE_PRIORITY = ( datetime.datetime, datetime.date, uuid.UUID, dict, list, set, float, int, str, ) def _structure_union(value: Any, union_types: Tuple[Type[T]]) -> Optional[T]: results = {} for a_type in union_types: try: results[a_type] = structure(value, a_type) except ValueError: pass for a_type in _STRUCTURE_UNION_TYPE_PRIORITY: if a_type in results: return results[a_type] return None def _get_types_from_object_or_its_constructor(goal_type): own_hints = get_type_hints(goal_type) if own_hints: return own_hints return get_type_hints(goal_type.__init__) def _try_structure_object(value: Any, goal_type: Any) -> Any: try: return goal_type( **{ k: structure(v, _get_types_from_object_or_its_constructor(goal_type)[k]) for k, v in value.items() } ) except (KeyError, ValueError): pass if issubclass(goal_type, dict): dict_value_type = goal_type.__args__[1] return {k: structure(v, dict_value_type) for k, v in value.items()} return None def _try_convert_string_to_decimal(value): try: return decimal.Decimal(value) except decimal.InvalidOperation as ex: raise ValueError from ex _STRUCTURE_STR_GOAL_TYPE_TO_CONVERSION_MAP: Dict[Type, Callable] = { int: int, float: float, decimal.Decimal: _try_convert_string_to_decimal, datetime.datetime: datetime.datetime.fromisoformat, datetime.date: datetime.date.fromisoformat, uuid.UUID: uuid.UUID, } def _try_structure_str(value: str, goal_type: Any) -> Any: conversion = _STRUCTURE_STR_GOAL_TYPE_TO_CONVERSION_MAP.get(goal_type) if conversion: try: return conversion(value) except ValueError as ex: raise ValueError( f"Could not convert {value} of type {type(value)} into a {goal_type}." ) from ex if hasattr(goal_type, "mro") and enum.Enum in goal_type.mro(): if value in goal_type.__members__: return goal_type[value] try: return getattr(str, goal_type) except TypeError as ex: raise ValueError( f"Could not convert {value} of type {type(value)} into a {goal_type} enum." ) from ex return value def _try_structure_int(value: int, goal_type: Any) -> Union[int, decimal.Decimal, str]: if goal_type == decimal.Decimal: return decimal.Decimal(value) if goal_type == str: return str(value) return value def _try_structure_float( value: float, goal_type: Any ) -> Union[float, decimal.Decimal, None]: if goal_type == decimal.Decimal: return decimal.Decimal(value) if goal_type == float: return value return None def _try_structure_list(value: List[Any], goal_type: Any) -> List[Any]: list_content_type = goal_type.__args__[0] return [structure(v, list_content_type) for v in value] def _try_structure_set(value: Set[Any], goal_type: Any) -> Set: set_content_type = goal_type.__args__[0] return set(structure(v, set_content_type) for v in value) def _try_structure_tuple(value: Tuple[Any], goal_type: Any) -> Tuple: tuple_content_types = goal_type.__args__ return tuple(structure(value[i], t) for i, t in enumerate(tuple_content_types)) # When structuring values the first value in each pair is used as a condition # which if true will attempt to structure the value using the second item in # the pair. Both items in the pair will be called with the value as the first # argument and the "goal type" as the second argument. # The order of this list of pairs denotes what order values will be structured # by. _STRUCTURE_VALUE_CONDITION_CONVERSION_PAIRS: Iterable[Tuple[Callable, Callable]] = [ (lambda v, gt: isinstance(v, dict), _try_structure_object), (lambda v, gt: getattr(gt, "_name", None) == "Tuple", _try_structure_tuple), (lambda v, gt: getattr(gt, "_name", None) == "Set", _try_structure_set), (lambda v, gt: getattr(gt, "_name", None) == "List", _try_structure_list), (lambda v, gt: isinstance(v, float), _try_structure_float), (lambda v, gt: isinstance(v, int), _try_structure_int), (lambda v, gt: isinstance(v, str), _try_structure_str), ] def _structure_value(value: Any, goal_type: Type[T]) -> T: for condition, conversion in _STRUCTURE_VALUE_CONDITION_CONVERSION_PAIRS: if condition(value, goal_type): # This could be a good place for PEP 572 the assignment operator # but since Python 3.7 is a target we shall do without. obj = conversion(value, goal_type) if obj is not None: return obj raise ValueError( f"Could not structure: {value} of type {type(value)} into {goal_type}" ) PKjgNs:!dataclass_structor/unstructure.pyimport decimal import datetime import enum import uuid from dataclasses import fields, is_dataclass from typing import Any, Callable, Iterable, Tuple def unstructure(value: Any) -> Any: """Returns dictionary, composed of simple types, given a value of a particular type. :param value: An object that you would like to convert into a serializable object. Usage:: >>> import datetime >>> import dataclass_structor >>> dataclass_structor.unstructure(datetime.date(2018, 9, 5)) "2018-09-05" """ for condition, conversion in _UNSTRUCTURE_VALUE_CONDITION_CONVERSION_PAIRS: if condition(value): return conversion(value) raise ValueError(f"Could not unstructure: {value}") _UNSTRUCTURE_VALUE_CONDITION_CONVERSION_PAIRS: Iterable[Tuple[Callable, Callable]] = [ (lambda v: v is None, lambda v: v), (lambda v: isinstance(v, str), lambda v: v), (lambda v: isinstance(v, float), lambda v: v), (lambda v: isinstance(v, int), lambda v: v), (lambda v: isinstance(v, decimal.Decimal), str), # `str` is a constructor here (lambda v: isinstance(v, uuid.UUID), str), (lambda v: isinstance(v, enum.Enum), lambda v: v.name), ( lambda v: isinstance(v, (datetime.datetime, datetime.date)), lambda v: v.isoformat(), ), (lambda v: isinstance(v, list), lambda v: [unstructure(i) for i in v]), (lambda v: isinstance(v, tuple), lambda v: tuple([unstructure(i) for i in v])), ( lambda v: isinstance(v, set), lambda v: set([unstructure(i) for i in v]), # pylint: disable=R1718 ), ( lambda v: isinstance(v, dict), lambda value: {k: unstructure(v) for k, v in value.items()}, ), ( is_dataclass, lambda v: {f.name: unstructure(getattr(v, f.name)) for f in fields(v)}, ), ( lambda v: hasattr(v, "__slots__"), lambda v: {f: unstructure(getattr(v, f)) for f in v.__slots__}, ), ] PK/M>11*dataclass_structor-0.0.6.dist-info/LICENSEMIT License Copyright (c) 2018 Douglas Anderson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HPO(dataclass_structor-0.0.6.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!HO+dataclass_structor-0.0.6.dist-info/METADATAU[oH~_q0/Jd)@ݒVCԞŞwN@ h#59-:^phP2'c6 f[{mSm%?٥on\5a넽Q ƚ\9mp_&jJpeN%ggl]L/ mJ16\4(Q cm|&c ){67B;0~CY\J~*^mV B2+&A{}Z"Wڿ=\\U4Bpe)VA8Yߝ!<1zx /\m"c ~]lwa)Sݩ(%/JL>Q*Si>o6b}q &hVEk-Ձ.Ks-}OqyQ?ԁko\CבjQ0bl n̿2pKK&!uJ ;=Ԗdy&cW<xaӔȢ40 P.bxOZ3ϝK]slpm39D>z%9пܤ!)68h r5n[| ˭8u]t)ʼnܙ?cެͪ?j*_PKzN dataclass_structor/__init__.pyPKjgN()n66dataclass_structor/structure.pyPKjgNs:!ddataclass_structor/unstructure.pyPK/M>11*i dataclass_structor-0.0.6.dist-info/LICENSEPK!HPO($dataclass_structor-0.0.6.dist-info/WHEELPK!HO+x%dataclass_structor-0.0.6.dist-info/METADATAPK!HicZ)(dataclass_structor-0.0.6.dist-info/RECORDPKF*