In this section, some typical problems and their associated
nob best practices are listed.
Don’t multiply smart (key) accesses¶
nob does a simple Python lookup when it is called with full paths, e.g.
n['/kennel/dogs']. However, it performs a smart accesses whenever
it is called with a simple key, e.g. (
n.dogs) (read the Quickstart
section if you haven’t or need a refresher). Smart accesses perform a full recursive
search of the tree, since it does not know in advance where the keyword you’re
looking for is. This can make them expensive on large trees.
In practice, say you have the following file:
root: System: Library: Frameworks: Python.framework: Versions: 2.7: bin: python2.7 3.7: bin: python3.7
If you want to access the name
'python2.7', you can take advantage of
instead of writing the full path:
The smart access will be a bit slower however, so in some performance-limiting cases you should keep that in mind. What you should never do is this:
Here, you’re performing a smart access to find
n['root'], then a new one on
n['root']['System'], etc… Each of these is expensive, and the result can be very slow.
Save sub-trees (
Any sub-tree can be efficiently saved as a Python object, and you should take advantage of this. In the example above, say you want to acces the executable values successively. You could write:
However, each key access on the root (
n) is expensive. It would be more efficient to write:
nv = n['Versions'] nv['2.7']['bin'][:] nv['3.7']['bin'][:]
Now, a single intial key access is performed on the root tree. Each final key access is performed
on the much smaller subtree
NobView object), and is much faster.
Filter by value and not by key¶
Often you’ll want to access some data (a sub-tree, or a value) that depends on another value.
nob is well equipped for dealing with keys, but less so for values. Let’s look at an example.
Say we want the sub-tree and the name of the alpha dog in this example:
city: Paris kennel: name: Dogtopia employees: 5 dogs: - name: Rex age: 3 alpha: False - name: Beethoven age: 8 alpha: True
This can be achieved simply with the following:
n = Nob(open('file.yml').read()) alpha_tree = [nv for nv in n.dogs if nv.alpha[:]] alpha_name = [nv for nv in n.dogs if nv.alpha[:]].name[:]
What if we want to know how old Rex is?
age = [nv for nv in n.dogs if nv.name[:] == 'Rex'].age[:]
Another syntax is to unpack the single value in a 1-uple (single-valued tuple).
rex, = [nv for nv in n.dogs if nv.name[:] == 'Rex'] age = rex.age[:]
Alternatively, an equivalent and more “functional” approach:
age = next(filter(lambda nv: nv.name[:] == 'Rex', n['dogs'])).age[:]
Error when dumping a Nob to yaml¶
If you try to dump the a Nob object directly, instead of extracting its data
[:], i.e. if you write:
this will seem like it works, but in fact yaml will serialize the full Nob object here, making it unreadable as Nob object afterwards. When trying to re-read this file, you’ll get a cryptic error ending with:
yaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object:nob.nob.Nob'
TL;DR: don’t forget the
Comparing trees of paths¶
If you wish to compare two trees by their paths, since
Path objects are
hashable, you can convert build sets with them. E.g.:
n, m = Nob(...), Nob(...) # The paths that are in both n and m set(n.paths) & set(m.paths) # The paths that are in n but not m set(n.paths) - set(m.paths)