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. Sonob_object['bin']
yields aNobView
object.If there is a
[:]
, you are accessing the underlying data (in pure Python).nob_object['bin'][:]
yieldspython2.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¶
n['/path/to/key']
will always workn['key']
will work if `key` is unambiguousn.key
will work ifkey
is unambiguous andkey
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 ton
, son.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.