.. _cookbook: Cookbook ======== In this section, some typical problems and their associated :py:`nob` best practices are listed. Don't multiply smart (key) accesses ----------------------------------- :py:`nob` does a simple Python lookup when it is called with full paths, *e.g.* :py:`n['/kennel/dogs']`. However, it performs a smart accesses whenever it is called with a simple key, *e.g.* (:py:`n.dogs`) (read the :ref:`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: .. code-block:: yaml root: System: Library: Frameworks: Python.framework: Versions: 2.7: bin: python2.7 3.7: bin: python3.7 If you want to access the name :py:`'python2.7'`, you can take advantage of smart access: .. code-block:: python n['2.7']['bin'][:] instead of writing the full path: .. code-block:: python n['/root/System/Library/Frameworks/Python.framework/Versions/2.7/bin'][:] 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: .. code-block:: python n['root']['System']['Library']['Frameworks']['Python.framework']['Versions']['2.7']['bin'] Here, you're performing a smart access to find :py:`n['root']`, then a new one on :py:`n['root']['System']`, etc... Each of these is expensive, and the result can be very slow. Save sub-trees (:py:`NobView` objects) -------------------------------------- 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: .. code-block:: python n['2.7']['bin'][:] n['3.7']['bin'][:] However, each key access on the root (:py:`n`) is expensive. It would be more efficient to write: .. code-block:: python 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 :py:`nv` (a :py:`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. :py:`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: .. code-block:: yaml 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: .. code-block:: python n = Nob(open('file.yml').read()) alpha_tree = [nv for nv in n.dogs if nv.alpha[:]][0] alpha_name = [nv for nv in n.dogs if nv.alpha[:]][0].name[:] What if we want to know how old Rex is? .. code-block:: python age = [nv for nv in n.dogs if nv.name[:] == 'Rex'][0].age[:] Another syntax is to unpack the single value in a 1-uple (single-valued tuple). .. code-block:: python rex, = [nv for nv in n.dogs if nv.name[:] == 'Rex'] age = rex.age[:] Alternatively, an equivalent and more "functional" approach: .. code-block:: python age = next(filter(lambda nv: nv.name[:] == 'Rex', n['dogs'])).age[:] .. _error_nob_yaml: Error when dumping a Nob to yaml -------------------------------- If you try to dump the a Nob object directly, instead of extracting its data *via* :py:`[:]`, *i.e.* if you write: .. code-block:: python yaml.dump(n, fh) 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: .. code-block:: 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 :py:`[:]` here! Comparing trees of paths ------------------------ If you wish to compare two trees by their paths, since :py:`Path` objects are hashable, you can convert build sets with them. *E.g.*: .. code-block:: python 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)