.. _quickstart: Quickstart ========== Let's look at what `nob `_ can do on some practical examples. Installation ------------ First, we must install :py:`nob`. The package is hosted on `PyPI `_, and installation should work fine with :code:`pip`: .. code-block:: bash pip install nob Installation in a virtual environment, or with the :code:`-U` option, or other methods is left as an exercise to the reader. Input and output ---------------- Suppose you're working with this file: .. code-block:: yaml city: Paris kennel: name: Dogtopia employees: 5 dogs: - name: Rex age: 3 alpha: False - name: Beethoven age: 8 alpha: True To create a :py:`Nob` from this YAML (or identically JSON) file, simply read it and feed the data to the constructor: .. code-block:: python 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 :py:`Nob` object, you can use: .. code-block:: python 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 :py:`[:]` here, don't forget it! See :ref:`error_nob_yaml` for details. Basic Manipulation ------------------ The variable :py:`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. :py:`nob` offers a useful class :py:`NobView` to this end, that handles identically for the most part as the main Nob, but changes performed on a :py:`NobView` affect the main :py:`Nob` instance that it is linked to. In practice, any access to a key of :py:`n` yields a :py:`NobView` instance, *e.g.*: .. code-block:: python nv1 = n['/city'] >>> NobView(/city) nv2 = n['city'] >>> NobView(/city) nv3 = n.city >>> NobView(/city) nv1 == nv2 == nv3 >>> True Note that a *full path* :py:`'/city'`, as well as a simple key :py:`'city'` are valid identifiers. Simple keys can also be called as attributes, using :py:`n.city`. Internally, paths are represented by dedicated objects: :ref:`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 :py:`[:]` operator: .. code-block:: python nv1[:] >>> 'Paris' n.city[:] >>> 'Paris' .. note:: This is an important marker: - If there is no :py:`[:]`, you are manipulating the tree-like structure of the data. So :py:`nob_object['bin']` yields a :py:`NobView` object. - If there is a :py:`[:]`, you are accessing the underlying data (in pure Python). :py:`nob_object['bin'][:]` yields :py:`python2.7` in our example. To assign a new value to this node, you can do it directly on the :py:`NobView` instance: .. code-block:: python n.city = 'London' nv1[:] >>> 'London' n[:]['city'] >>> 'London' .. note:: Of course, because of how Python variables work, you cannot simply assign the value to :py:`nv1`, as this would just overwrite its contents: .. code-block:: python nv1 = 'London' nv1[:] >>> 'London' n.city[:] >>> 'Paris' If you find yourself with a :py:`NobView` object that you would like to edit directly, you can use the :py:`.set` method: .. code-block:: python nv1 = n.city nv1.set('London') n['city'][:] >>> 'London' Because nested objects can contain both dicts and lists, integers are sometimes needed as keys: .. code-block:: python 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. .. code-block:: python n.kennel.dogs.0 >>> SyntaxError: invalid syntax Reserved keywords ***************** Some keywords are reserved, due to the inner workings of :py:`Nob`. To access a key that has a name equal to a reserved keyword, use item access (:py:`n['key']` but not :py:`n.key`). To view reserved keywords, use: .. code-block:: python 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. :py:`n['/path/to/key']` will **always** work 2. :py:`n['key']` will work **if `key` is unambiguous** 3. :py:`n.key` will work if :py:`key` is unambiguous **and** :py:`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 :py:`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: .. code-block:: python nested_dict['city'] If you are looking for *e.g.* the kennel name, you would need to write: .. code-block:: python nested_dict['kennel']['name'] For deep nested objects however, this can be a chore, and become very difficult to read. :py:`nob` helps you here by supplying a smart method for finding unique keys: .. code-block:: python n['employees'] >>> NobView(/kennel/employees) n.employees >>> NobView(/kennel/employees) Note that attribute access :py:`n.employees` behaves like simple key access :py:`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: .. code-block:: python n.name >>> KeyError: Identifier alpha yielded 3 results instead of 1 Oops! Because :py:`name` is not unique (it appears 3 times in the tree), :py:`n.name` is not specific, and :py:`nob` wouldn't know which one to return. In this instance, depending on which :py:`name` we're looking for we could use: .. code-block:: python 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: - :py:`kennel` is unique to :py:`n`, so :py:`n.kennel` is valid. Then, :py:`name` would still be ambiguous, so a full path is used: :py:`['/name']` - Similarly, :py:`n.dogs` is valid. It arrives at a list, so next we need to specify the index, as we would in a list. Finally, :py:`name` is unique to each dog, so we can access it directly. Thats it! You now have all the keys to use :py:`nob` in it's most basic form. You can continue rueading to see more features that :py:`nob` has to offer, or skip to the :ref:`cookbook` section to see if a specific problem you have has a suggested best practice.