Skip to content

saska/trict

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Last release Platforms

trict

Python dictionary with extra stuff.

Starting off, if you're seeing this, please give feedback!

You can install with pip install trict or conda install -c saska trict (working on conda-forge). Doesn't currently have any dependencies (although you'll need pytest if you want to run the tests for some reason), only works with python>3.6 strictly because I like f-strings which I can reformat out if someone needs this to work on something else.

Tricktionaries (subclass of collections.UserDict) are dictionary-type things that have recursive (and other) helper things I've previously needed. Here's how they work:

You initialize a tricktionary with a normal dictionary.

>>> from trict import Trict

>>> t = Trict({
    'user': {
        'information': {
            'attribute': 'infonugget',
            'another_attribute': 'secondnugget'
        },
        'moreinformation': 'extranugget'
    }
})

You can then perform all sorts of cool operations with it, like period-separated setting and getting (or any-string-separated, you can specify key_sep as a keyword argument in the Trict constructor, default is .).

>>> t['user.information.attribute']
'infonugget'

Just like a python dictionary, Trict implements get(key, default=None) if you don't like KeyErrors.

When setting, the Trict will create intermediary keys for you:

>>> t['user.newinformation.newattribute'] = 'new'
>>> t['user.newinformation.newattribute']
'new'

Deleting also works as you'd expect, so you can also .pop(). You can also do any of this using sequences as keys.

>>> key = ['user', 'information', 'another_attribute']
>>> t[key]
'secondnugget'

If you want to flatten your dictionary for a csv export for example (use keys as the header), you can do that. The constructor arg key_sep also affects how keys are formatted here. The Tricktionary can be rebuilt by passing this type of flattened dictionary into the alternate from_flat_dict constructor with the same key_sep.

>>> t.flatten()
{
  'user.information.attribute': 'infonugget', 
  'user.information.another_attribute': 'secondnugget', 
  'user.moreinformation': 'extranugget', 
  'user.newinformation.newattribute': 'new'
}

Or do a complete traversal with traverse. It returns a generator yielding 2-tuples of (key-path, value).

>>> for k, v in t.traverse():
...     print(f"{k}: {v}")
['user']: {'information': {'attribute': 'infonugget', 'another_attribute': 'secondnugget'}, 'moreinformation': 'extranugget', 'newinformation': {'newattribute': 'new'}}
['user', 'information']: {'attribute': 'infonugget', 'another_attribute': 'secondnugget'}
['user', 'information', 'attribute']: infonugget
['user', 'information', 'another_attribute']: secondnugget
['user', 'moreinformation']: extranugget
['user', 'newinformation']: {'newattribute': 'new'}
['user', 'newinformation', 'newattribute']: new

If you just need the leaves, you can do that as well with a similar format.

>>> for k, v in t.leaves():
...     print(f"{k}: {v}")
['user', 'information', 'attribute']: infonugget
['user', 'information', 'another_attribute']: secondnugget
['user', 'moreinformation']: extranugget
['user', 'newinformation', 'newattribute']: new

You can also map the leaves (the actual values at the end of your dictionary) with a callable.

>>> t.map_leaves(lambda x: x + " - and there's more!")
>>> t.data
{
  'user': {
    'information': {
      'attribute': "infonugget - and there's more!", 
      'another_attribute': "secondnugget - and there's more!"
    }, 
    'moreinformation': "extranugget - and there's more!", 
    'newinformation': {
      'newattribute': "new - and there's more!"
    }
  }
}

All the dictionary helper methods are also provided standalone. There's also an extra one, iter_keys that iterates over anything that's a key - helpful if there's a bunch of people writing in your codebase and you want to enforce say a regex naming convention on your keys or something.

If you're more of the "I need to make sure my API can handle most anything thrown at it!" type, you can define a mapper (using {new_key: [str or sequence]}, try and stick to one, it's prettier, example has both) and throw any dicts you receive into a Tricktionary and map them to the same format. Pretty handy if you need to take in documents in multiple formats, just make sure document x doesn't have different data from document y in the same key path.

>>> mapper = {
        'newkey': [
            'user.noninformation.nonattribute', # Doesn't exist
            'user.information.another_attribute' # Finds
        ],
        'othernewkey': [
            ['user', 'noninformation'], # Doesn't exist
            ['user', 'information'] # Finds
        ]
    }
>>> t.map_with_dict(mapper)
{
  'newkey': "secondnugget - and there's more!", 
  'othernewkey': {
    'attribute': "infonugget - and there's more!", 
    'another_attribute': "secondnugget - and there's more!"
  }
}

Similar functionality can be found in get_by_seq which also takes [str or sequence] and returns val if any of those keys exists.

About

Python dictionary with extra stuff

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages