dictregister provides an object that contains an ordered list of dictionaries with some functions to search and manage them.
Dictionaries are useful objects, as they can easily represent complex objects; being a basic language structure in Python they are very handy: as an instance, they are serialiable, and if you ever worked with JSON you are accustomed to see them around.
When dealing with more than one dictionary, namely a list of them, a problem arises: searching the list for dictionaries is complex and you usually write a bunch of repeated code to get the information you need.
dictregister acts as a standard Python list but can contain only dictionaries (actually objects implementing collections.Mapping); additionally, it provides functions to search and manage dictionaries by key, to manage single keys and to store more than one value for each key.
dictregister is a pure Python package, but its syntax has been heavily influenced by Django's query syntax, so Django users will find at home.
Indeed, dictregister acts like a small key/value database. Please note that there it stores values in memory and there is no optimization, so use it for small collections.
The DictRegister
object acts as a list
, so you can either initialize it empty
import dictregister
dr = dictregister.DictRegister()
or with an iterable object as an argument
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
and you can use any method of list
like append()
import dictregister
dr = dictregister.DictRegister()
dr.append({'x':1, 'y':2})
DictRegister
accepts only objects that inherit from the collections.Mapping
Abstract Base Class. If you try to insert an object that does not stick with this rule you will receive a TypeError
.
You can manage keys in batch mode with kadd()
, kreplace()
, and kremove()
.
Adding a key to each element is easy with kadd()
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
dr.kadd('z', 5)
dr == [{'x':1, 'y':2, 'z':5}, {'x':3, 'y':4, 'z':5}]
Please note that if you add more than a value to the same key you get a multiple-value element, which is treated in a special way. See the Multiple values section below.
When you remove keys you can do it unconditionally
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
dr.kremove('y')
dr == [{'x':1}, {'x':3}]
which removes all keys with that name. or you can specify a value, in which case only the elments that match both the key and the value will be removed.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
dr.kremove('y',4)
dr == [{'x':1, 'y':2}, {'x':3}]
Last, you can replace the value of a key
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
dr.kreplace('x',6)
dr == [{'x':6, 'y':2}, {'x':6, 'y':4}]
You can find a subset of dictionaries using dfilter()
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
filtdr = dr.dfilter(x=1)
filtdr == [{'x':1, 'y':2}]
You can pass as many conditions as you want to dfilter()
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dfilter(x=1)
filtdr == [{'x':1, 'y':2}, {'x':1, 'y':6}]
filtdr = dr.dfilter(x=1, y=2)
filtdr == [{'x':1, 'y':2}]
You can easily get only the first element of the filtering with dget()
. Remember that while dfilter()
silently accepts a search that returns no values, returning an empty DictRegister
, dget()
raises an IndexError
exception.
You can remove elements from a DictRegister
object with dremove()
, which returns a DictRegister
containing the removed elements.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dremove(x=1)
dr == [{'x':3, 'y':4}]
filtdr == [{'x':1, 'y':2}, {'x':1, 'y':6}]
Otherwise you obtain a new object with the elements removed dremove_copy()
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dremove_copy(x=1)
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}]
filtdr == [{'x':3, 'y':4}]
Last you can pop an element with dpop()
, which returns the first element matching the given conditions. Remember that dpop()
raises IndexError
if no matching element is found.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dpop(x=1)
dr == [{'x':3, 'y':4}, {'x':1, 'y':6}]
Remember that, being a list, DictRegister
also provides you a pop([i])
method that pops the element at index i
or the first element if i
is not specified.
Note that dfilter()
, dremove()
, and dremove_copy()
return a DictRegister
so you can easily chain calls.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dfilter(x=1).dremove_copy(y=2)
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}]
filtdr == [{'x':1, 'y':6}]
When using the advanced features of DictRegister
like filtering you can use a special syntax for keys, namely a key__operator
syntax.
The implicit operator is eq
, which matches all dictionaries with the given key with the given value.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dfilter(x__eq=3)
filtdr == [{'x':3, 'y':4}]
filtdr = dr.dfilter(x=3)
filtdr == [{'x':3, 'y':4}]
The inequality can be matched with ne
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6}])
filtdr = dr.dfilter(x__ne=1)
filtdr == [{'x':3, 'y':4}]
You can match dictionaries that contain or not a given key
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':6, 'z':8}])
filtdr = dr.dfilter(z__iskey=True)
filtdr == [{'x':1, 'y':6, 'z':8}]
filtdr = dr.dfilter(z__iskey=False)
filtdr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
The DictRegister
object can contain any dictionary with a single value for each key, like
import dictregister
dr = dictregister.DictRegister()
dr.append({'x':1, 'y':2})
If you store more than a value for a key, DictRegister
uses a set to host the values. You are free to append dictionaries with generic sequences, most notably lists and sets, as values. However remeber that DictRegister
does not consider the sequence itself as the value of the key, but the contained elements; so if you need to store a sequence as a value you have to store a set
that contains the sequence.
import dictregister
dr = dictregister.DictRegister([{'x':1, 'y':2}, {'x':3, 'y':4}])
dr == [{'x':1, 'y':2}, {'x':3, 'y':4}]
dr.kadd('x', 2)
dr == [{'x':set([1, 2]), 'y':2}, {'x':set([2, 3]), 'y':4}]
You can match multiple values with the in
and nin
operators. The first matches all dictionaries that contain the given key with the given value among its values, while nin
performs the opposite match.
import dictregister
dr = dictregister.DictRegister([{'x':set([1, 2]), 'y':2}, {'x':2, 'y':4}])
filtdr = dr.dfilter(x__in=2)
filtdr == [{'x':set([1, 2]), 'y':2}, {'x':2, 'y':4}]
As you can see DictRegister
treats keys with a single value and with multiple values in the same way.
pip install dictregister
Any form of contribution is warmly welcomed. Feel free to submit issues of to make changes and submit a pull request. being the first Python package I ship with all the bells and whistles like distutils, tests and friends, I gladly accept suggestions or corrections on this topic.
Many thanks to Jeff Knupp for his post Open Sourcing a Python Project the Right Way.
Many thanks to Audrey M. Roy for her cookiecutter and cookiecutter-pypackage tools, which heavily simplified the implementation of the whole thing.