{ "info": { "author": "", "author_email": "lapeyre@cerfacs.fr", "bugtrack_url": null, "classifiers": [ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# nob: the Nested OBject manipulator\n\nJSON is a very popular format for nested data exchange, and Object Relational\nMapping (ORM) is a popular method to help developers make sense of large JSON\nobjects, by mapping objects to the data. In some cases however, the nesting\ncan be very deep, and difficult to map with objects. This is where nob can be\nuseful: it offers a simple set of tools to explore and edit any nested data\n(Python native dicts and lists).\n\nFor more, checkout the [home page](gitlab.com/cerfacs/nob).\n\n## Usage\n\n### Instantiation\n\n`nob.Tree` objects can be instantiated directly from a Python dictionary:\n\n t = Tree({\n 'key1': 'val1',\n 'key2': {\n 'key3': 4,\n 'key4': {'key5': 'val2'},\n 'key5': [3, 4, 5]\n },\n 'key5': 'val3'\n })\n\nTo create a `Tree` from a JSON (or YAML) file, simply read it and feed the data\nto the constructor:\n\n import json\n with open('file.json') as fh:\n t2 = Tree(json.load(fh))\n\n import yaml\n with open('file.yml') as fh:\n t3 = Tree(yaml.load(fh))\n\nSimilarly, to create a JSON (YAML) file from a tree, you can use:\n\n with open('file.json', 'w') as fh:\n json.dump(t2.val, fh)\n\n with open('file.yml', 'w') as fh:\n yaml.dump(t3.val, fh)\n\n### Basic manipulation\n\nThe variable `t` now holds a tree, *i.e* the reference to the actual data. However,\nfor many practical cases it is useful to work with a subtree. `nob` offers a useful\nclass `TreeView` to this end. It handles identically for the most part as the main tree,\nbut changes performed on a `TreeView` affect the main `Tree` instance that it is linked\nto. In practice, any access to a key of `t` yields a `TreeView` instance, *e.g.*:\n\n tv1 = t['/key1'] # TreeView(/key1)\n tv2 = t['key1'] # TreeView(/key1)\n tv3 = t.key1 # TreeView(/key1)\n tv1 == tv2 == tv3 # True\n\nNote that a *full path* `'/key1'`, as well as a simple key `'key1'` are valid\nidentifiers. Simple keys can also be called as attributes, using `t.key1`.\n\nTo access the actual value that is stored in the nested object, simply use the `.val`\nmethod:\n\n tv1.val >>> 'val1'\n t.key1.val >>> 'val1'\n\nTo assign a new value to this node, you can do it directly on the TreeView instance:\n\n t.key1 = 'new'\n tv1.val >>> 'new'\n t.val['key1'] >>> 'new'\n\nOf course, because of how Python variables work, you cannot simply assign the value to\n`tv1`, as this would just overwrite it's contents:\n\n tv1 = 'new'\n tv1 >>> 'new'\n t.val['key1'] >>> 'val1'\n\nIf you find yourself with a `TreeView` object that you would like to edit directly,\nyou can use the `.set` method:\n\n tv1 = t.key1\n tv1.set('new')\n t.val['key1'] >>> 'new'\n\nBecause nested objects can contain both dicts and lists, integers are sometimes\nneeded as keys:\n\n t['/key2/key5/0'] >>> TreeView(/key2/key5/0)\n t.key2.key5[0] >>> TreeView(/key2/key5/0)\n t.key2.key5['0'] >>> TreeView(/key2/key5/0)\n\nHowever, since Python does not support attributes starting with an integer, there is\nno attribute support for lists. Only key access (full path, integer index or its\nstringified counterpart) are supported.\n\n### Smart key access\n\nIn a simple nested dictionary, the access to `'key1'` would be simply done with:\n\n nested_dict['key1']\n\nIf you are looking for *e.g.* `key3`, you would need to write:\n\n nested_dict['key2']['key3']\n\nFor deep nested objects however, this can be a chore, and become very difficult to\nread. `nob` helps you here by supplying a smart method for finding unique keys:\n\n t['key3'] >>> TreeView(/key2/key3)\n t.key3 >>> TreeView(/key2/key3)\n\nNote that attribute access `t.key3` behaves like simple key access `t['key3']`. This\nhas some implications when the key is not unique in the tree. Let's say *e.g.* we wish\nto access `key5`. Let's try using attribute access:\n\n t.key5 >>> KeyError: Identifier key5 yielded 3 results instead of 1\n\nOups! Because `key5` is not unique (it appears 3 times in the tree), `t.key5` is not\nspecific, and `nob` wouldn't know which one to return. In this instance, we have\nseveral possibilities, depending on which `key5` we are looking for:\n\n t.key4.key5 >>> TreeView(/key2/key4/key5)\n t.key2['/key5'] >>> TreeView(/key2/key5)\n t['/key5'] >>> TreeView(/key5)\n\nThere is a bit to unpack here:\n\n - The first `key5` is unique in the `TreeView` `t.key4` (and `key4` is itself\n unique), so `t.key4.key5` finds it correctly.\n - The second is complex: `key2` is unique, but `key5` is still not unique to `t.key2`.\n There is not much advantage compared to a full path access `t['/key2/key5']`.\n - The last cannot be resolved using keys in its path, because there are none. The \n only solution is to use a full path.\n\n## Other tree tools\n\n**Paths:** any `Tree` (or `TreeView`) object can introspect itself to find all its valid paths:\n\n t.paths >>> [Path('/'),\n Path('/key1'),\n Path('/key2'),\n Path('/key2/key3'),\n Path('/key2/key4'),\n Path('/key2/key4/key5'),\n Path('/key2/key5'),\n Path('/key2/key5/0'),\n Path('/key2/key5/1'),\n Path('/key2/key5/2'),\n Path('/key5')]\n\n**Find:** in order to easily search in this path list, the `.find` method is available:\n\n t.find('key5') >>> [Path('/key2/key4/key5'),\n Path('/key2/key5'),\n Path('/key5')]\n\nThe elements of these lists are not strings, but `Path` objects, as described\nbelow.\n\n**Iterable:** any tree or tree view is also iterable, yielding its children:\n\n [tv for tv in t.key2] >>> [TreeView(/key2/key3),\n TreeView(/key2/key4),\n TreeView(/key2/key5)]\n\n**Copy:** to make an independant copy of a tree, use its `.copy()` method:\n\n t_cop = t.copy()\n t == t_cop >>> True\n t_cop.key1 = 'new_val'\n t == t_cop >>> False\n\nA new standalone tree can also be produced from any tree view:\n\n t_cop = t.key2.copy()\n t_cop == t.key2 >>> True\n t_cop.key3 = 5\n t_cop == t.key2 >>> False\n\n## Path\n\nAll paths are stored internally using the `nob.Path` class. Paths are full\n(w.r.t. their `Tree` or `TreeView`), and are in essence a list of the keys\nconstituting the nested address. They can however be viewed equivalently as\na unix-type path string with `/` separators. Here are some examples\n\n p1 = Path(['key1'])\n p1 >>> Path(/key1)\n p2 = Path('/key1/key2')\n p2 >>> Path(/key1/key2)\n p1 / 'key3' >>> Path(/key1/key3)\n p2.parent >>> Path(/key1)\n p2.parent == p1 >>> True\n 'key2' in p2 >>> True\n [k for k in p2] >>> ['key1', 'key2']\n p2[-1] >>> 'key2'\n len(p2) >>> 2\n\nThese can be helpful to manipulate paths yourself, as any full access with\na string to a `Tree` or `TreeView` object also accepts a `Path` object. So say\nyou are accessing the keys in `list_of_keys` at one position, but that thet also\nexist elsewhere in the tree. You could use *e.g.*:\n\n root = Path('/path/to/root/of/keys')\n [t[root / key] for key in list_of_keys]\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://gitlab.com/cerfacs/nob", "keywords": "JSON,YAML,Nested Object", "license": "", "maintainer": "", "maintainer_email": "", "name": "nob", "package_url": "https://pypi.org/project/nob/", "platform": "", "project_url": "https://pypi.org/project/nob/", "project_urls": { "Homepage": "https://gitlab.com/cerfacs/nob" }, "release_url": "https://pypi.org/project/nob/0.2.1/", "requires_dist": null, "requires_python": "", "summary": "Nested OBject manipulations", "version": "0.2.1" }, "last_serial": 5731574, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "9b0dd426265a8113c73deefaf2b725ca", "sha256": "c112e3f7f5616a3ced7719de560b5d5201db12f51b5261380de5b4ed46e2f5c5" }, "downloads": -1, "filename": "nob-0.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "9b0dd426265a8113c73deefaf2b725ca", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 6834, "upload_time": "2019-08-15T15:28:06", "url": "https://files.pythonhosted.org/packages/bd/ae/3fd26a8292f748a22460c0449410dac341b0d0c0b523b664c64fd109d55b/nob-0.1.0-py2.py3-none-any.whl" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "7e17cc15eb208647344bc2980806d211", "sha256": "a63226be28393b6a4cc4c71979490e47a8721c71c32fe6166ffce65918587940" }, "downloads": -1, "filename": "nob-0.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7e17cc15eb208647344bc2980806d211", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7063, "upload_time": "2019-08-19T08:06:46", "url": "https://files.pythonhosted.org/packages/d2/fc/e23ca18103659fce39af7ee11e6cf76fda2fc0e1b5d91292a7979be7d62d/nob-0.1.1-py2.py3-none-any.whl" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "e78598cfb0ff6b0d6c429c75a4b8454d", "sha256": "e006d869b811d50db811ed1d3473bd19b7c1c618b7abba3322d4f7750dc46214" }, "downloads": -1, "filename": "nob-0.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "e78598cfb0ff6b0d6c429c75a4b8454d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7030, "upload_time": "2019-08-19T15:42:59", "url": "https://files.pythonhosted.org/packages/d5/d1/061ef02a9e21c8317b4a3aedcf824488bd21dff5af1764db3100630575e7/nob-0.1.2-py2.py3-none-any.whl" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "b5b88ea6f0c2aa09e1762fe65d9e1e61", "sha256": "f60fd1f192b4b0aa8c5ce92c7d9a02aecf09313e0ffd6381d69edbc5e9dbb936" }, "downloads": -1, "filename": "nob-0.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "b5b88ea6f0c2aa09e1762fe65d9e1e61", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7590, "upload_time": "2019-08-23T06:19:24", "url": "https://files.pythonhosted.org/packages/75/b0/129590f8f5020ba76b3bfef1005465218923a6500b2ad8614f8b5fdd8967/nob-0.2.0-py2.py3-none-any.whl" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "304c9974b6c4459b07e68d1fcf0ec6d0", "sha256": "353e5f556666df529285d0d20401702f727732e4e1ff6c8fc3e8a3317dc947c6" }, "downloads": -1, "filename": "nob-0.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "304c9974b6c4459b07e68d1fcf0ec6d0", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 8536, "upload_time": "2019-08-26T15:28:58", "url": "https://files.pythonhosted.org/packages/1c/81/2cba16bd7abe37251e8ce55f47d7c0da9c501f577f709d152e809ebde659/nob-0.2.1-py2.py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "304c9974b6c4459b07e68d1fcf0ec6d0", "sha256": "353e5f556666df529285d0d20401702f727732e4e1ff6c8fc3e8a3317dc947c6" }, "downloads": -1, "filename": "nob-0.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "304c9974b6c4459b07e68d1fcf0ec6d0", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 8536, "upload_time": "2019-08-26T15:28:58", "url": "https://files.pythonhosted.org/packages/1c/81/2cba16bd7abe37251e8ce55f47d7c0da9c501f577f709d152e809ebde659/nob-0.2.1-py2.py3-none-any.whl" } ] }