Skip to main content

Handy python classes for manipulating json data, providing syntactic sugar for less verbose, easier to write code.

Project description

traversify

Handy python classes for manipulating json data, providing syntactic sugar for less verbose, easier to write code.

Traverser class allows one to:

  • traverse complex trees of data with dotted syntax rather than the verbose dictionary dereferencing.
  • treat nodes on the tree as lists even if they are singleton dictionaries, eliminating a lot of type-checking code.
  • add or delete branches of the tree with simple dotted syntax.
  • treat missing keys on the tree as None rather than throwing a key exception, much as JavaScript returns undefined.
  • linkage to Filter class (defined next) for powerful tree comparisons or tree pruning.

Filter class allows one to:

  • define a set of criteria for comparing two partially incongruous trees by limiting the sets of fields compared.
  • apply said criteria to prune a tree of any unwanted fields.

Traverser

Pass tree data to Traverser, either as a list, dictionary, json string or any class offering a json method, and the resultant object will provide the syntactic sugar for traversing with dotted syntax, treating singleston nodes as lists:

>>> from traversify import Traverser
>>> obj = Traverser({'id': 1, 'username': 'jdoe'})
>>> obj.id
1
>>> obj.username
'jdoe'
>>> obj.bad_key is None
True
>>> [node.id for node in obj]
[1]
>>> obj[0].id
1
>>> {'id': 1, 'username': 'jdoe'} in obj
True

Not only can singletons be addressed as lists, but append and extend methods are available to turn singletons into lists on the fly:

>>> obj = Traverser({'id': 1})
>>> obj.append({'id': 2})
>>> obj.extend([{'id': 3, 'id': 4}])
>>> [node.id for node in obj]
[1, 2, 3, 4]

At any time, a Traverser instance will return the underlying value when called:

>>> obj = Traverser({'id': 1})
>>> obj()
{'id': 1}

The tree can be updated using dotted syntax. Note that by default, a Traverser instance makes a deepcopy of the json data so that there are no unintended side effects:

>>> data = {'id': 1, 'username': 'jdoe'}
>>> obj = Traverser(data)
>>> obj.id = 2
>>> del obj.username
>>> obj()
{'id': 2}
>>> data
{'id': 1, 'username': 'jdoe'}

However, if the side-effect of updating the data passed is desired (perhaps due to memory constaints), then pass deepcopy=False:

>>> data = {'id': 1}
>>> obj = Traverser(data, deepcopy=False)
>>> obj.id = 2
>>> obj()
{'id': 2}
>>> data
{'id': 2}

In case there are keys that are not identifiers, then dictionary dereferencing can still be used:

>>> obj = Traverser({'@xsi.type': 'textarea'})
>>> obj['@xsi.type']
'textarea'

The get method allows traversing multiple levels in one call, using dots to set off the levels:

>>> obj = Traverser({'root': {'username': 'any'}})
>>> obj.get('root.username')
'any'

Also, the get method supports dot-escaping so that keys containing dots can still be traversed:

>>> obj = Traverser({'@xsi.type': 'textarea'})
>>> obj.get('@xsi..type')
'textarea'

There's a set method that will update a node multiple levels down and even build out branches that aren't already there:

>>> obj = Traverser({'stats': {'id': 1}})
>>> obj.set('stats.id', 2)
>>> obj()
{'stats': {'id': 2}}
>>> obj.set('users.0.username', 'any')
>>> obj()
{'stats': {'id': 2}, 'users': [{'username': 'any'}]}

To save the trouble of importing json and using dumps, there's a handy to_json method:

>>> obj = Traverser({'id': 1})
>>> obj.to_json()
'{"id": 1}'

Filter

Often one needs to compare two trees without taking into account irrelavant fields, like when records in the tree have ids, but a new record doesn't have it yet. Filter provides a way to make this less verbose by providing blacklist and whitelist attributes for controlled comparison:

>>> from traversify import Traverser, Filter
>>> id_exclude_filter = Filter(blacklist='id')
>>> record = Traverser({'id': 1, 'username': 'jdoe'})
>>> id_exclude_filter.are_equal(record, {'username': 'jdoe'})
True

The same filter can be used to prune a tree of its unwanted fields:

>>> id_exclude_filter.prune(record)
>>> record()
{'username': 'jdoe'}

If a filter is passed while creating a Traverser instance, then ==, in and the prune() method will use it to do the comparison or pruning:

>>> record = Traverser({'id': 1, 'username': 'jdoe'}, filter=Filter(blacklist='id'))
>>> record == {'username': 'jdoe'}
True
>>> {'username': 'jdoe'} in record
True
>>> record.prune()
>>> record()
{'username': 'jdoe'}

Traverser's prune method will accept a filter to override the default (or supply one not already supplied):

>>> record = Traverser({'id': 1, 'username': 'jdoe'})
>>> record.prune(filter=Filter(blacklist='id'))
>>> record()
{'username': 'jdoe'}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

traversify-1.1.2.tar.gz (5.5 kB view hashes)

Uploaded Source

Built Distribution

traversify-1.1.2-py3-none-any.whl (20.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page