Package: python-dict-wrapper
Python Dict WrapperThis is a simple class that exposes a dictionary's keys as class attributes,
making for less typing when accessing dictionary values.  This class also
enforces that the dictionary's overallshapeis maintained.A common use of this class may be in retrieving and updating model objects
from web services (i.e. RESTful web services) where the shape of the model
object must be kept intact between when it is retrieved and when it is saved.For instance, if used withrequests, the
output of a request'sjson()call can be wrapped and the resulting object
will behave in much the same manner as a real model object.  The values
can be manipulated and laterunwrappedto be sent back the server using
a requestspost()call.Using the python_dict_wrapper is pretty simple.  Youwrap()a dictionary
(or list).  Then you manipulate and/or query it.  Finally, you canunwrap()to get the dictionary (or list) back.A trivial example:import requests
from python_dict_wrapper import wrap, unwrap

actor_dict = requests.get('http://ficticious_actor_database_site.com/actors/c/carell_steve').json()

# Returns:
# {
#    "name": "Steve Carell",
#    "career": [{
#        "medium": "TV",
#        "title": "The Office"
#    }, {
#        "medium": "MOVIE",
#        "title": "Bruce Almighty"
#    }]
#}

actor = wrap(actor_dict)
actor.career[1].title = "Despicable Me"
unwrapped_actor = unwrap(actor)

requests.post('http://ficticious_actor_database_site.com/actors/c/carell_steve', data=unwrapped_actor)Installationpython_dict_wrapper is available on PyPi, so the easiest way to install it
is by using pip:$ pip install python-dict-wrapperfunction make_wrapper(**kargs)make_wrapperis a factory function for quickly instantiating aDictWrapperfrom keyword arguments.  It's easier to demonstrate:>>> from python_dict_wrapper import make_wrapper
>>>
>>> person = make_wrapper(first_name='Steve', last_name='Carell', occupation='actor')
>>> person
<DictWrapper: {'first_name': 'Steve', 'last_name': 'Carell', 'occupation': 'actor'}>
>>> person.last_name
'Carell'function wrap(data, strict=False, key_prefix=None, mutable=True)wrapis a factory function for generating either a DictWrapper or a
ListWrapper.  It has one required argument and three optional ones:data - A Python dictionary or a list of dictionaries that needs to be wrapped.
If data is a dictionary, this method will return a DictWrapper instance.  If
it's a list, the function will return a ListWrapper instance.  This argument
is required.strict - An optional boolean that indicates if the wrapper should enforce
types when setting attribute values.key_prefix - A string or list of strings that contains characters that
dictionary keys should be prefixed with before they become attributes.mutable - A boolean indicating whether the DictWapper should be mutable or not.This is a convenience function for when you have a data object and don't want
to bother checking if it's a dictionary or a list.>>> from python_dict_wrapper import wrap
>>>
>>> person_dict = {'first_name': 'Steve', 'last_name': 'Carell', 'occupation': 'actor'}
>>>
>>> person = wrap(person_dict)
>>>
>>> person
<DictWrapper: {'first_name': 'Steve', 'last_name': 'Carell', 'occupation': 'actor'}>
>>> person.occupation
'actor'function unwrap(wrapped_item)Theunwrapfunction will return the original item that was wrapped.>>> from python_dict_wrapper import wrap, unwrap
>>> data_dict = {'first_name': 'Steve', 'last_name': 'Carell'}
>>> id(data_dict)
4497764480
>>> wrapped_data_dict = wrap(data_dict)
>>> id(wrapped_data_dict)
4498248224
>>> wrapped_data_dict
<python_dict_wrapper.DictWrapper object at 0x10c1dd220>
>>> unwrapped_data_dict = unwrap(wrapped_data_dict)
>>> unwrapped_data_dict is data_dict
True
>>> unwrapped_data_dict
{'first_name': 'Steve', 'last_name': 'Carell'}Theunwrapfunction will work on bothDictWrapperitems as well asListWrapperitems.  If the item passed tounwrapis not aDictWrapperor aListWrapper,unwrapwill just return the item untouched.DictWrapperobjects manipulate the original dictionary that they wrap so
unwrapping is technically unnecessary.  That said, unwrap is available in the
event a reference to the original dictionary is lost or goes out of scope.function add_attribute(wrapped_item, attribute_name, attribute_value)Theadd_attributefunction can be used to add an attribute to a DictWrapper
after it has been instantiated.  It can be used if the original dictionary is
no longer available.>>> from python_dict_wrapper import wrap, add_attribute
>>> auth_config = wrap({'username': 'john@doe.com', 'password': 'itza!secret'})
>>> add_attribute(auth_config, 'host', 'ldap.doe.com')
>>> auth_config.host
'ldap.doe.com'function del_attribute(wrapped_item, attribute_name)Conversely,del_attributeremoves an existing attribute from an existing
DictWrapper.  The del_attribute will return what the attribute's last value was
before being removed.>>> from python_dict_wrapper import wrap, del_attribute
>>> auth_config = wrap({'username': 'john@doe.com', 'password': 'itza!secret'})
>>> del_attribute(auth_config, 'password')
'itza!secret'
>>> hasattr(auth_config, 'password')
Falseclass DictWrapper(data, strict=False, key_prefix=None, mutable=True)Like the wrap function, eachDictWrapperinstance takes one required argument
and three optional ones:dict - A Python dictionary that the wrapper will use as it's source. This
argument is required.strict - An optional boolean that indicates if the wrapper should enforce
types when setting attribute values.key_prefix - A string or list of strings that contains characters that
dictionary keys should be prefixed with before they become attributes.mutable - A boolean indicating whether the DictWapper should be mutable or not.AttributesOnce aDictWrapperinstance has been created, the keys of it's source
dictionary will be exposed as attributes.  So for example if aDictWrapperis instantiated with the following dictionary:>>> from dict_wrapper import wrap
>>> address_dict = {'street': '221B Baker Street', 'city': 'London', 'country': 'UK'}
>>> address = wrap(address_dict)The keys:street,city, and 'country' will be exposed as attributes ofaddress>>> address.street
'221B Baker Street'
>>> address.city
'London'
>>> address.country
'UK'The attributes are both readable and writeable, so you can update the values simply by
assigning to them:>>> address.country = "United Kingdom"
>>> address.country
'United Kingdom'If thestrictargument to the constructor was set toTrue, then theDictWrapperwill enforce that that when you assign a new value to an attribute, it must be the same
Type as the original dictionary value.>>> address = wrap(address_dict, strict=True)
>>> address.street = 221
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "dict_wrapper.py", line 62, in __setattr__
    raise TypeError("Value for %s must be a %s, not %s" % (
TypeError: Value for street must be a str, not intIf thekey_prefixargument to the constructor is set to a string or list of strings,
attributes in the dictionary are searched without their prefixes.  This is typically used
for dictionaries that have keys that cannot be represented in attributes.  Here's an
example:>>> the_dict = {'@timestamp': '2020-04-19 05:00:00', 'author': 'Arthur Conan Doyle'}
>>>
>>> entry = wrap(the_dict)
>>> entry.timestamp
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "python_dict_wrapper.py", line 49, in __getattr__
    self._check_for_bad_attribute(key)
  File "python_dict_wrapper.py", line 87, in _check_for_bad_attribute
    raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, key))
AttributeError: 'DictWrapper' object has no attribute 'timestamp'
>>>
>>>
>>> entry = DictWrapper(the_dict, key_prefix='@')
>>> entry.timestamp
'2020-04-19 05:00:00'MethodsDictWrapperinstances have two methods:to_json()andto_dict().to_json(pretty=False)Converts the dictionary values to a JSON string.  If theprettyargument is set toTrue,
the returned JSON will be multi-lined and indented with 4 characters.  If it's false, the
returned JSON will a single-line of text.to_dict()Converts theDictWrapperback to a Python dictionary.NestingDictWrapperinstances should be able to handle nested dictionaries and lists without
issue.  It automatically wraps any nested dictionaries in their ownDictWrapperinstances for you.>>> shelock_dict = {
...     'name': 'Sherlock Holmes',
...     'address': {
...             'street': '221B Baker Street',
...             'city': 'London',
...             'country': 'UK'
...     }
... }
>>> sherlock = DictWrapper(sherlock_dict)
>>> sherlock.address.country = 'United Kingdom'
>>> print(sherlock.to_json(pretty=True))
{
    "name": "Sherlock Holmes",
    "address": {
        "street": "221B Baker Street",
        "city": "London",
        "country": "United Kingdom"
    }
}class ListWrapper(data, strict=False, key_prefix=None, mutable=True)TheListWrapperis a "list" version of theDictWrapper.  It is used by theDictWrapperwhen nesting lists within dictionary values.  TheListWrapperis a subclass of a built-in Python list and behaves almost exactly like a Python
list with one exception.  When retrieving items out of the list if the item is
a dictionary, it will wrap it in aDictWrapper.  If the item in question is
a Python list, it will wrap it in another ListWrapper.>>> from python_dict_wrapper import ListWrapper
>>> the_list = [
...     'one',
...     [1, 2, 3],
...     {'color': 'blue'}
... ]
>>> wrapped_list = ListWrapper(the_list)
>>> wrapped_list[0]
'one'
>>> wrapped_list[1]
<ListWrapper: [1, 2, 3]>
>>> wrapped_list[1][2]
3
>>> wrapped_list[2]
<DictWrapper: {'color': 'blue'}>
>>> wrapped_list[2].color
'blue'MutabilityIf theDictWrapperis instantiated withmutableset to True (default), theDictWrapperwill be mutable, meaning the attribute can be changed.  However, ifmutableis set to False when the DictWrapper is instantiated, it will be immutable.
You will not be able to change any of the attributes (or nested attributes).  Any
ListWrappers that result from lists within the underlying dict will also be immutable.
You will not be able to add/remove from them.>>> from python_dict_wrapper import wrap
>>> auth_config = wrap({'username': 'john@doe.com', 'password': 'itza!secret'}, mutable=False)
>>> auth_config.password
'itza!secret'
>>> auth_config.password = 'super!secret'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "python_dict_wrapper.py", line 78, in __setattr__
    raise AttributeError("can't set attribute")
AttributeError: can't set attributePerformanceDictWrapperandListWrapperinstances lazy evaluate on the original dicts/lists
that they are given when wrapped.  As a result performance of these classes should
be roughly the same as their native counterparts.