PK!rrdeserialize/__init__.py"""A module for deserializing data to Python objects.""" #pylint: disable=unidiomatic-typecheck #pylint: disable=protected-access #pylint: disable=too-many-branches #pylint: disable=wildcard-import import enum import functools import typing from typing import Any, Callable, Dict, List, Optional, Union from deserialize.decorators import ignore, _should_ignore, key, _get_key, parser, _get_parser from deserialize.exceptions import DeserializeException, InvalidBaseTypeException from deserialize.type_checks import * def deserialize(class_reference, data): """Deserialize data to a Python object.""" if not isinstance(data, dict) and not isinstance(data, list): raise InvalidBaseTypeException("Only lists and dictionaries are supported as base raw data types") return _deserialize(class_reference, data) def _deserialize(class_reference, data): """Deserialize data to a Python object, but allow base types""" # Shortcut out if we have already got the matching type. # We shouldn't need the isinstance check against typing._GenericAlias since # it should return False if it isn't, however, it has overridden the check, # so calling isinstance against typing._GenericAlias throws an exception. To # avoid this, we make sure it isn't one before continuing. if not isinstance(class_reference, typing._GenericAlias) and isinstance(data, class_reference): return data if isinstance(data, dict): return _deserialize_dict(class_reference, data) if isinstance(data, list): return _deserialize_list(class_reference, data) if issubclass(class_reference, enum.Enum): try: return class_reference(data) #pylint:disable=bare-except except: #pylint:enable=bare-except # This will be handled at the end pass raise DeserializeException(f"Cannot deserialize '{type(data)}' to '{class_reference}'") def _deserialize_list(class_reference, list_data): if not isinstance(list_data, list): raise DeserializeException(f"Cannot deserialize '{type(list_data)}' as a list.") if not is_list(class_reference): raise DeserializeException(f"Cannot deserialize a list to '{class_reference}'") list_content_type_value = list_content_type(class_reference) output = [] for item in list_data: deserialized = _deserialize(list_content_type_value, item) output.append(deserialized) return output def _deserialize_dict(class_reference, data): """Deserialize a dictionary to a Python object.""" hints = typing.get_type_hints(class_reference) if len(hints) == 0: raise DeserializeException(f"Could not deserialize {data} into {class_reference} due to lack of type hints") class_instance = class_reference() for attribute_name, attribute_type in hints.items(): if _should_ignore(class_reference, attribute_name): continue property_key = _get_key(class_reference, attribute_name) parser_function = _get_parser(class_reference, property_key) property_value = parser_function(data.get(property_key)) # Check for optionals first. We check if it's None, finish if so. # Otherwise we can hoist out the type and continue if is_optional(attribute_type): if property_value is None: setattr(class_instance, attribute_name, None) continue else: attribute_type = optional_content_type(attribute_type) # If the types match straight up, we can set and continue if type(property_value) == attribute_type: setattr(class_instance, attribute_name, property_value) continue # Check if we have something we need to parse further or not. # If it is a base type (i.e. not a wrapper of some kind), then we can # go ahead and parse it directly without needing to iterate in any way. if is_base_type(attribute_type): custom_type_instance = _deserialize(attribute_type, property_value) setattr(class_instance, attribute_name, custom_type_instance) continue # Lists and dictionaries remain if is_list(attribute_type): setattr(class_instance, attribute_name, _deserialize_list(attribute_type, property_value)) continue if is_dict(attribute_type): if not isinstance(property_value, dict): raise DeserializeException(f"Value '{property_value}' is type '{type(property_value)}' not 'dict'") # If there are no values, then the types automatically do match if len(property_value) == 0: setattr(class_instance, attribute_name, property_value) continue key_type, value_type = dict_content_types(attribute_type) result = {} for item_key, item_value in property_value.items(): if type(item_key) != key_type: raise DeserializeException(f"Key '{item_key}' is type '{type(item_key)}' not '{key_type}'") # If the types match, we can just set it and move on if type(item_value) == value_type: result[item_key] = item_value continue # We have to deserialize (it will throw on failure) result[item_key] = _deserialize(value_type, item_value) setattr(class_instance, attribute_name, result) continue raise DeserializeException(f"Unexpected type '{type(property_value)}' for attribute '{attribute_name}'. Expected '{attribute_type}'") return class_instance PK!ŀ deserialize/decorators.py"""Decorators used for adding functionality to the library.""" def ignore(property_name): """A decorator function for marking keys as those which should be ignored.""" def store_key_map(class_reference): """Store the key map.""" try: _ = class_reference.__deserialize_ignore_map__ except AttributeError: setattr(class_reference, "__deserialize_ignore_map__", {}) class_reference.__deserialize_ignore_map__[property_name] = True return class_reference return store_key_map def _should_ignore(class_reference, property_name): """Check if a property should be ignored.""" try: return class_reference.__deserialize_ignore_map__.get(property_name, False) except AttributeError: return False def key(property_name, key_name): """A decorator function for mapping key names to properties.""" def store_key_map(class_reference): """Store the key map.""" try: _ = class_reference.__deserialize_key_map__ except AttributeError: setattr(class_reference, "__deserialize_key_map__", {}) class_reference.__deserialize_key_map__[property_name] = key_name return class_reference return store_key_map def _get_key(class_reference, property_name): """Get the key for the given class and property name.""" try: return class_reference.__deserialize_key_map__.get(property_name, property_name) except AttributeError: return property_name def parser(key_name, parser_function): """A decorator function for mapping parsers to key names.""" def store_parser_map(class_reference): """Store the parser map.""" try: _ = class_reference.__deserialize_parser_map__ except AttributeError: setattr(class_reference, "__deserialize_parser_map__", {}) class_reference.__deserialize_parser_map__[key_name] = parser_function return class_reference return store_parser_map def _get_parser(class_reference, key_name): """Get the parser for the given class and keu name.""" def identity_parser(value): """This parser does nothing. It's simply used as the default.""" return value try: return class_reference.__deserialize_parser_map__.get(key_name, identity_parser) except AttributeError: return identity_parser PK!5r&&deserialize/exceptions.py"""Module for all exceptions used in the library.""" class DeserializeException(Exception): """Represents an error deserializing a value.""" pass class InvalidBaseTypeException(DeserializeException): """An error where the "base" type to be deserialized was invalid.""" pass PK!4deserialize/type_checks.py"""Convenience checks for typing.""" import typing #pylint: disable=protected-access def is_base_type(type_value): """Check if a type is a base type or not.""" if isinstance(type_value, typing._GenericAlias): return False return attribute_type_name(type_value) is None def is_optional(type_value): """Check if a type is an optional type.""" if not isinstance(type_value, typing._GenericAlias): return False if len(type_value.__args__) != 2: return False return type_value.__args__[1] == type(None) def optional_content_type(type_value): """Strip the Optional wrapper from a type. e.g. Optional[int] -> int """ if not is_optional(type_value): raise TypeError(f"{type_value} is not an Optional type") return type_value.__args__[0] def is_list(type_value): """Check if a type is a list type.""" if not isinstance(type_value, typing._GenericAlias): return False return type_value._name == "List" def list_content_type(type_value): """Strip the List wrapper from a type. e.g. List[int] -> int """ if not is_list(type_value): raise TypeError(f"{type_value} is not a List type") return type_value.__args__[0] def is_dict(type_value): """Check if a type is a dict type.""" if not isinstance(type_value, typing._GenericAlias): return False return type_value._name == "Dict" def dict_content_types(type_value): """Return the content types for a dictionay. e.g. Dict[str, int] -> (str, int) """ if not is_dict(type_value): raise TypeError(f"{type_value} is not a List type") return type_value.__args__[0], type_value.__args__[1] def attribute_type_name(type_value): """Return the name of the attribute type (if there is one). If this is a non-typing type, None will be returned. """ if not isinstance(type_value, typing._GenericAlias): return None return type_value._name PK!WI::#deserialize-0.7.0.dist-info/LICENSEMIT License Copyright (c) Dale Myers. All rights reserved. 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 SOFTWAREPK!HnHTU!deserialize-0.7.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H+i"Z$deserialize-0.7.0.dist-info/METADATAWmoF_1 r (ҩT@ETޔPym-:ǵ3>BU!qֳ3҉B8qNt={+Q9JDnr܍.۪f;3*UfHNS%pARm=וM59r9vD?<|7:VkG *;Oivኑ5u߸$Q/VQ?( @m[K##MxGs?fqᑂd<ĴZC :wQpi񋛙fq$@+8tCZ܀,.w',W2\DFo=JVoLV]l-ti{/ʯgoz L)Gw>` +׵LO\!e9D~%YTM)c_n֋I`(Ar̈wwL7_ QV]:ʑ*0)eZ<|5]"^L`g$ s a 9eZzw֛pzUlȼ]Yqð-Ex`r,0ddpvda"?(l=PRnX=1 8]E*33 n HV8m‡gB('+ tZ,ң5 r fapRsx]7P*L!cznkKj2J*RN-e'4ߑ.קjKQ`k;f``jm p: ׆s` 8HޤNB۝rlW`s|W>k.oG:dHth_mS.J崫z Ր^tri-^ pr{OO> ,#~Ka{0@[_юv$C˖W@51G%Hׁoa}UQ>$Oj;Pºb˶ν9;=KV- gd_AKv a{d 2- J_G[Fq /^0ˍmTE&!<=xc)Rp@@円eK@xrXem@iZuՔa!p1kzg8ג$]btdhؼlX_~̦~/\â1D h&,|,&;DTݫmtHVZ| x jD`C0xe@),l^E_g'ˆR/:lQ,ZڃeGx$z#IjůU^ቬ6uY]iT~&Hsw=s+k^!3<7MmvK}Y m8=#Mk-WL74mӹ7M(+n4yG9QPK!rrdeserialize/__init__.pyPK!ŀ deserialize/decorators.pyPK!5r&&^ deserialize/exceptions.pyPK!4!deserialize/type_checks.pyPK!WI::#)deserialize-0.7.0.dist-info/LICENSEPK!HnHTU!B.deserialize-0.7.0.dist-info/WHEELPK!H+i"Z$.deserialize-0.7.0.dist-info/METADATAPK!H;tM|"-6deserialize-0.7.0.dist-info/RECORDPK]7