Skip to main content

Python Dict Wrapper

This 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 overall shape is 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 with requests, the output of a request's json() 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 later unwrapped to be sent back the server using a requests post() call.

Using the python_dict_wrapper is pretty simple. You wrap() a dictionary (or list). Then you manipulate and/or query it. Finally, you can unwrap() 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)

Installation

python_dict_wrapper is available on PyPi, so the easiest way to install it is by using pip:

$ pip install python-dict-wrapper

function make_wrapper(**kargs)

make_wrapper is a factory function for quickly instantiating a DictWrapper from 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)

wrap is 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)

The unwrap function 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'}

The unwrap function will work on both DictWrapper items as well as ListWrapper items. If the item passed to unwrap is not a DictWrapper or a ListWrapper, unwrap will just return the item untouched.

DictWrapper objects 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)

The add_attribute function 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_attribute removes 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')
False

class DictWrapper(data, strict=False, key_prefix=None, mutable=True)

Like the wrap function, each DictWrapper instance 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.

Attributes

Once a DictWrapper instance has been created, the keys of it's source dictionary will be exposed as attributes. So for example if a DictWrapper is 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 of address

>>> 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 the strict argument to the constructor was set to True, then the DictWrapper will 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 int

If the key_prefix argument 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'

Methods

DictWrapper instances have two methods: to_json() and to_dict().

to_json(pretty=False)

Converts the dictionary values to a JSON string. If the pretty argument is set to True, 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 the DictWrapper back to a Python dictionary.

Nesting

DictWrapper instances should be able to handle nested dictionaries and lists without issue. It automatically wraps any nested dictionaries in their own DictWrapper instances 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)

The ListWrapper is a "list" version of the DictWrapper. It is used by the DictWrapper when nesting lists within dictionary values. The ListWrapper is 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 a DictWrapper. 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'

Mutability

If the DictWrapper is instantiated with mutable set to True (default), the DictWrapper will be mutable, meaning the attribute can be changed. However, if mutable is 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 attribute

Performance

DictWrapper and ListWrapper instances 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.

Metadata

Release files for python-dict-wrapper 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-dict-wrapper 1.1.1
File Size Uploaded
python_dict_wrapper-1.1.1.tar.gz 45.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-dict-wrapper 1.1.1
File Interpreter ABI Platform
python_dict_wrapper-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 77.2 kB

Release files / python_dict_wrapper-1.1.1.tar.gz

Download URL python_dict_wrapper-1.1.1.tar.gz
Size 45.9 kB
Tags Source
SHA-256 checksum
How to use checksums
92d3ea9ae506525f7c8917638bdcc7559e84d1714b35188bb48e0558a8b75afc
BLAKE2b-256 checksum
How to use checksums
a65a428760338a243cfc7a61fe97238346c88f941eff3db6e9b1b73a68382fff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.13

Release files / python_dict_wrapper-1.1.1-py3-none-any.whl

Download URL python_dict_wrapper-1.1.1-py3-none-any.whl
Size 31.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c860da5c94fc7b8a6e5bf237e8bc20f463fa6e62ee4f53bc1520848896716570
BLAKE2b-256 checksum
How to use checksums
945a36d7d849ebff76c4089a3878f5dafb8e02ba1190002c70eb21066a2b348f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.13

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 release files

1.1

2 release files

1.0.1

2 release files

1.0

2 release files

0.9

2 release files

0.8

2 release files

0.7

2 release files

0.6

2 release files

0.5

2 release files

0.4

2 release files

0.3

2 release files

0.2

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page