Quickstart

Let’s look at what nob can do on some practical examples.

Installation

First, we must install nob. The package is hosted on PyPI, and installation should work fine with pip:

pip install nob

Installation in a virtual environment, or with the -U option, or other methods is left as an exercise to the reader.

Input and output

Suppose you’re working with this file:

city: Paris
kennel:
 name: Dogtopia
 employees: 5
 dogs:
 - name: Rex
   age: 3
   alpha: False
 - name: Beethoven
   age: 8
   alpha: True

To create a Nob from this YAML (or identically JSON) file, simply read it and feed the data to the constructor:

import json
with open('file.json') as fh:
    n2 = Nob(json.load(fh))

import yaml
with open('file.yml') as fh:
    n3 = Nob(yaml.load(fh))

Similarly, to create a JSON (YAML) file from a Nob object, you can use:

with open('file.json', 'w') as fh:
    json.dump(n2[:], fh)

with open('file.yml', 'w') as fh:
    yaml.dump(n3[:], fh)

Warning

Note the [:] here, don’t forget it! See Error when dumping a Nob to yaml for details.

Basic Manipulation

The variable n now holds a Nob, i.e the reference to the actual data. For many practical cases it is useful to work with a subset of the nob. nob offers a useful class NobView to this end, that handles identically for the most part as the main Nob, but changes performed on a NobView affect the main Nob instance that it is linked to. In practice, any access to a key of n yields a NobView instance, e.g.:

nv1 = n['/city']         >>> NobView(/city)
nv2 = n['city']          >>> NobView(/city)
nv3 = n.city             >>> NobView(/city)
nv1 == nv2 == nv3        >>> True

Note that a full path '/city', as well as a simple key 'city' are valid identifiers. Simple keys can also be called as attributes, using n.city. Internally, paths are represented by dedicated objects: Path.

Warning

Nob only handles JSON compatible files, i.e where dict keys are only strings. Mix and match at your own risk.

To access the actual value that is stored in the nested object, simply use the [:] operator:

nv1[:]                   >>> 'Paris'
n.city[:]                >>> 'Paris'

Note

This is an important marker:

  • If there is no [:], you are manipulating the tree-like structure of the data. So nob_object['bin'] yields a NobView object.

  • If there is a [:], you are accessing the underlying data (in pure Python). nob_object['bin'][:] yields python2.7 in our example.

To assign a new value to this node, you can do it directly on the NobView instance:

n.city = 'London'
nv1[:]                   >>> 'London'
n[:]['city']             >>> 'London'

Note

Of course, because of how Python variables work, you cannot simply assign the value to nv1, as this would just overwrite its contents:

nv1 = 'London'
nv1[:]               >>> 'London'
n.city[:]            >>> 'Paris'

If you find yourself with a NobView object that you would like to edit directly, you can use the .set method:

nv1 = n.city
nv1.set('London')
n['city'][:]             >>> 'London'

Because nested objects can contain both dicts and lists, integers are sometimes needed as keys:

n['/kennel/dogs/0']      >>> NobView(/kennel/dogs/0)
n.kennel.dogs[0]         >>> NobView(/kennel/dogs/0)
n.kennel.dogs['0']       >>> NobView(/kennel/dogs/0)

However, since Python does not support attributes starting with an integer, there is no attribute support for lists. Only key access (full path, integer index or its stringified counterpart) are supported.

n.kennel.dogs.0         >>> SyntaxError: invalid syntax

Reserved keywords

Some keywords are reserved, due to the inner workings of Nob. To access a key that has a name equal to a reserved keyword, use item access (n['key'] but not n.key). To view reserved keywords, use:

n.reserved()             >>> ['_MutableMapping__marker', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry', '_data', '_find_all', '_find_unique', '_getitem_slice', '_raw_data', '_root', '_tree', 'clear', 'copy', 'find', 'get', 'items', 'keys', 'np_deserialize', 'np_serialize', 'paths', 'pop', 'popitem', 'reserved', 'root', 'setdefault', 'update', 'val', 'values']

Manipulation Summary

  1. n['/path/to/key'] will always work

  2. n['key'] will work if `key` is unambiguous

  3. n.key will work if key is unambiguous and key is not a reserved keyword, and key is a legal python attribute (no spaces, doesn’t start with a number, no dots…)

So you can use a Nob like a nested dictionary at all times (method 1.). Methods 2 and 3 enable fast access except when they don’t apply.

Smart Key Access

In a simple nested dictionary, you would read the city name with:

nested_dict['city']

If you are looking for e.g. the kennel name, you would need to write:

nested_dict['kennel']['name']

For deep nested objects however, this can be a chore, and become very difficult to read. nob helps you here by supplying a smart method for finding unique keys:

n['employees']           >>> NobView(/kennel/employees)
n.employees              >>> NobView(/kennel/employees)

Note that attribute access n.employees behaves like simple key access n['employees']. This has some implications when the key is not unique in the tree. Let’s say e.g. we wish to figure out the first dog’s name. Let’s try using attribute access:

n.name                   >>> KeyError: Identifier alpha yielded 3 results instead of 1

Oops! Because name is not unique (it appears 3 times in the tree), n.name is not specific, and nob wouldn’t know which one to return. In this instance, depending on which name we’re looking for we could use:

n.kennel['/name']        >>> NobView(/kennel/name)
n.dogs[0].name           >>> NobView(/kennel/dogs/0)
n.dogs[1].name           >>> NobView(/kennel/dogs/1)

There is a bit to unpack here:

  • kennel is unique to n, so n.kennel is valid. Then, name would still be ambiguous, so a full path is used: ['/name']

  • Similarly, n.dogs is valid. It arrives at a list, so next we need to specify the index, as we would in a list. Finally, name is unique to each dog, so we can access it directly.

Thats it! You now have all the keys to use nob in it’s most basic form. You can continue rueading to see more features that nob has to offer, or skip to the Cookbook section to see if a specific problem you have has a suggested best practice.