{ "info": { "author": "Richard Bann", "author_email": "richardbann@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.6" ], "description": "# rel2tree\n\nConvert your list of data into `JSON` serializable structure.\n\n## Motivation\n\nLet's suppose you have a set of data given as a list of dicts:\n\n```py\nimport json\n\n[\n {\"name\": \"Jane\", \"city\": \"New York\", \"sales\": 23},\n {\"name\": \"Joe\", \"city\": \"New York\", \"sales\": 11},\n {\"name\": \"Jane\", \"city\": \"Chicago\", \"sales\": 21},\n {\"name\": \"Jane\", \"city\": \"New York\", \"sales\": 4},\n {\"name\": \"Joe\", \"city\": \"New York\", \"sales\": 13},\n {\"name\": \"Joe\", \"city\": \"Chicago\", \"sales\": 31},\n {\"name\": \"Jane\", \"city\": \"New York\", \"sales\": 7},\n]\n```\n\nYou may want a nice summary, something like this:\n\n```json\n[\n {\n \"name\": \"Jane\",\n \"cities\": [\n {\n \"city\": \"New York\",\n \"sales\": 34\n },\n {\n \"city\": \"Chicago\",\n \"sales\": 21\n }\n ],\n \"sum\": 55\n },\n {\n \"name\": \"Joe\",\n \"cities\": [\n {\n \"city\": \"New York\",\n \"sales\": 24\n },\n {\n \"city\": \"Chicago\",\n \"sales\": 31\n }\n ],\n \"sum\": 55\n }\n]\n```\n\nThis can be done relatively easily by iterating over the data\nset and building the final structure.\n\n```py\nsummary = {}\nfor record in data:\n this_person = summary.setdefault(record[\"name\"], {\n \"name\": record[\"name\"],\n \"cities\": {},\n \"sum\": 0,\n })\n this_person_cities = this_person[\"cities\"].setdefault(record[\"city\"], {\n \"city\": record[\"city\"],\n \"sum\": 0,\n })\n this_person_cities[\"sum\"] += record[\"sales\"]\n this_person[\"sum\"] += record[\"sum\"]\nsummary = list(summary.values())\nfor person in summary:\n person[\"cities\"] = list(person[\"cities\"].values())\n\nprint(json.dumps(summary))\n```\n\nAlthough the above code works well, but it has some problems.\n\n- Not declarative: by looking at the code it is not trivial to tell the final data\n structure.\n- Error-prone.\n- The complexity grows with more complex business logic\n or by adding an additional level.\n- Not reusable.\n\nLet's see how you do it with `rel2tree`:\n\n```py\nfrom rel2tree import f # NOQA\n\nsummary = f.groupby(lambda x: x[\"name\"], f.dict({\n \"name\": f.groupkey(),\n \"cities\": f.groupby(lambda x: x[\"city\"], f.dict({\n \"city\": f.groupkey(),\n \"sum\": f.map(lambda x: x[\"sales\"]).t(sum)\n })),\n \"sum\": f.map(lambda x: x[\"sales\"]).t(sum)\n}))\n\nprint(json.dumps(summary(data)))\n```\n\n## Tutorial\n\n### `map`, `sort`, `filter`, `distinct`\n\nThe only object one can import from `rel2tree` is `f`, which is of type `F`\nso we will call it an `F` object.\n`f` is callable, but - on it's own does nothing:\n\n```py\nprint(f(2))\n# 2\n```\n\nLet's say we have a list of numbers (`numbers`) and we want\nto duplicate all of it's elements. This can be done in many ways:\n\n- using a list comprehension:\n ```py\n out = [2 * x for x in numbers]\n ```\n- using map:\n ```py\n out = map(lambda x: 2 * x, numbers)\n ```\n- defining a function (for reusability)\n ```py\n import functools\n dup = functools.partial(map, lambda x: 2 * x)\n out = dup(numbers)\n ```\n\nUsing an `f` it looks like this:\n\n```py\nnumbers = range(15)\ndup = f.map(lambda x: 2 * x)\nout = dup(numbers)\n```\n\nThis simply made our third approach a little more terse.\n\nNow what if our task is to add 1 to each element after\nduplication? Can we reuse our `dup` function? As\nthe result of `f.map` has the same type as `f`, we can\nuse map again:\n\n```py\ndupplus1 = dup.map(lambda x: x + 1)\n```\n\n`f.sort(fnc)` sorts our list based on the value of `fnc`\napplied to the items (just as the `key` argument of python's)\n`sorted`. `f.filter(fnc)` keeps only those `i` items, where\n`fnc(i)` is ture(ish). These methods also return `F`s\n(internally the type of `f` is `F`) so they are chainable.\nThe `F` below first duplicates, then filters out big\nnumbers and finally sorts them. (`f.sort`, without a function sorts the elements.)\n\n```py\nf.map(lambda x: 2 * x).filter(lambda x: x < 10).sort()\n```\n\n### `dict`\n\nBack to our `numbers`, but with the desired output of\n\n```json\n{\n \"even\": [0, 2, 4, 6, 8, 10, 12, 14],\n \"odd\": [1, 3, 5, 7, 9, 11, 13]\n}\n```\n\nWe can combine the dict method to achive this:\n\n```py\nsummary = f.dict({\n \"even\": f.filter(lambda x: (x % 2 == 0)),\n \"odd\": f.filter(lambda x: (x % 2 == 1)),\n})\n```\n\nIf the dictionary values are `F` objects, those objects will be called with\nthe input list to form the final values, otherwise the values will be left as is.\n\n### `groupby`\n\nTo generalize the above example, we can group our numbers based on the remainder\ndevided by, say, 3:\n\n```py\nsummary = f.groupby(lambda x: x % 3)\n# [[0, 3, 6, 9, 12], [1, 4, 7, 10, 13], [2, 5, 8, 11, 14]]\n```\n\nTo make it more informative, the desired output should be:\n\n```json\n[\n { \"remainder\": 0, \"numbers\": [0, 3, 6, 9, 12] },\n { \"remainder\": 1, \"numbers\": [1, 4, 7, 10, 13] },\n { \"remainder\": 2, \"numbers\": [2, 5, 8, 11, 14] }\n]\n```\n\nThis can be done by using `groupkey`:\n\n```py\nsummary = f.groupby(lambda x: x % 3, f.dict({\n \"remainder\": f.groupkey(),\n \"numbers\": f\n}))\n```\n\n`f.groupkey(level=0)` gives the deepest level group key, while `f.groupkey(1)`\nis the one level above group key in case of nested `groupby`'s.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/richardbann/rel2tree", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "rel2tree", "package_url": "https://pypi.org/project/rel2tree/", "platform": "", "project_url": "https://pypi.org/project/rel2tree/", "project_urls": { "Homepage": "https://github.com/richardbann/rel2tree" }, "release_url": "https://pypi.org/project/rel2tree/7.0.0/", "requires_dist": null, "requires_python": "", "summary": "Convert a list of records to a JSON-like structure", "version": "7.0.0" }, "last_serial": 5808933, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "f6236c78a9d8e7e3b0b0ff6d6f566586", "sha256": "4f303ee02a2c86505809ae46ab275afa053e1d37b6e50ad7f6595b54e2e4d8c7" }, "downloads": -1, "filename": "rel2tree-0.1.tar.gz", "has_sig": false, "md5_digest": "f6236c78a9d8e7e3b0b0ff6d6f566586", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2960, "upload_time": "2017-03-08T08:30:22", "url": "https://files.pythonhosted.org/packages/1a/22/55208375c6a8c3982a0f0bed3cbf1ff89ab72ac2f398f5298e2c7254144f/rel2tree-0.1.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "6acde94cc4791fe3b201d9668abef30d", "sha256": "8f868f1ae87a97cebb9aa0e2ff8b715bc9eb560b1037a71287364a1a7c28b5ba" }, "downloads": -1, "filename": "rel2tree-1.1.0.tar.gz", "has_sig": false, "md5_digest": "6acde94cc4791fe3b201d9668abef30d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3265, "upload_time": "2017-03-15T09:41:20", "url": "https://files.pythonhosted.org/packages/6e/2b/e821914be541c6fadeafb13f7f95ed9d0440d154e82e7e1841a7d60203b3/rel2tree-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "5c0fcb6d153882454a7fc60ebdbdb5b7", "sha256": "b8b8adc66efe0c781abe6bfc6ae676a1785c6dd902e22e0390bbddd2c0a89c7e" }, "downloads": -1, "filename": "rel2tree-1.1.1.tar.gz", "has_sig": false, "md5_digest": "5c0fcb6d153882454a7fc60ebdbdb5b7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3271, "upload_time": "2017-03-15T14:45:14", "url": "https://files.pythonhosted.org/packages/de/eb/59ee921a9e137283bdf382b464357aaed23f95c97ea97e3b9891d783da4b/rel2tree-1.1.1.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "09f25d285f0f102fd5c3094b506b11c9", "sha256": "32603a2adbadb6126c9d4bf4b8ee6da16e8facf8a5a8313a414d03225c66c036" }, "downloads": -1, "filename": "rel2tree-2.0.0.tar.gz", "has_sig": false, "md5_digest": "09f25d285f0f102fd5c3094b506b11c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2696, "upload_time": "2017-03-16T13:22:35", "url": "https://files.pythonhosted.org/packages/f2/47/c1909e335c60a98f7394130da4f4a1597304719ad733209a8d9056d51950/rel2tree-2.0.0.tar.gz" } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "c71bbd20237043ac8dea33c51cb9a7bf", "sha256": "4347fe6b7e514337af1fa5719cdfbcc02ff9afc67ed43f4e9757ebf57c8b2bcd" }, "downloads": -1, "filename": "rel2tree-2.0.1.tar.gz", "has_sig": false, "md5_digest": "c71bbd20237043ac8dea33c51cb9a7bf", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2711, "upload_time": "2017-03-30T22:52:47", "url": "https://files.pythonhosted.org/packages/ca/20/612880233e4dc4777221d87e952f3a178c20fbe77d02fd442c7755a6ff93/rel2tree-2.0.1.tar.gz" } ], "3.0.0": [ { "comment_text": "", "digests": { "md5": "123c4fac69070c636ee8b9adb9a659b5", "sha256": "9b9fd7f8bd701d9986e24244791983426b5faa8ebc9f4140aad8cd2233bbfa6c" }, "downloads": -1, "filename": "rel2tree-3.0.0.tar.gz", "has_sig": false, "md5_digest": "123c4fac69070c636ee8b9adb9a659b5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2381, "upload_time": "2018-02-12T15:34:02", "url": "https://files.pythonhosted.org/packages/f6/34/b5b963f7e79941b84a35b14605944aeaa8f6045dc652dd2f9b706f73a733/rel2tree-3.0.0.tar.gz" } ], "5.0.0": [ { "comment_text": "", "digests": { "md5": "40e15efb4b3a467ca385d82965be64bd", "sha256": "9f4a006412df21d876cfa7bebf163135646eef9a3a8f5f8950a7a396e6a04f60" }, "downloads": -1, "filename": "rel2tree-5.0.0.tar.gz", "has_sig": false, "md5_digest": "40e15efb4b3a467ca385d82965be64bd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2564, "upload_time": "2018-11-06T14:04:39", "url": "https://files.pythonhosted.org/packages/61/5f/f1d0fe42c07139bf8db29f5af3daa4736126390dce4e6bda6b73cfa25ebf/rel2tree-5.0.0.tar.gz" } ], "6.0.0": [ { "comment_text": "", "digests": { "md5": "9bef5577350cdb728e854676523f5700", "sha256": "abf90fa12f0406b872e4a4333a55641884b8b5203feb97165040a3fc22ad0d10" }, "downloads": -1, "filename": "rel2tree-6.0.0.tar.gz", "has_sig": false, "md5_digest": "9bef5577350cdb728e854676523f5700", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1961, "upload_time": "2019-09-05T14:05:46", "url": "https://files.pythonhosted.org/packages/b9/3a/895be1f2e56848550d9c63a01388c2bf04ca5884b77614bf90fff5ad18c5/rel2tree-6.0.0.tar.gz" } ], "6.0.1": [ { "comment_text": "", "digests": { "md5": "cd0ee0e2f2b856546b74b5ee4c0fb15c", "sha256": "aae0a62f45d5a7d096b9eb02d389d244331ed5196fff1f97e001bdddfb1c1896" }, "downloads": -1, "filename": "rel2tree-6.0.1.tar.gz", "has_sig": false, "md5_digest": "cd0ee0e2f2b856546b74b5ee4c0fb15c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4153, "upload_time": "2019-09-05T18:04:21", "url": "https://files.pythonhosted.org/packages/0d/b0/4f49bbebe941cde1e25d31dd38ff919805e16bdd249bd26a4900661391a8/rel2tree-6.0.1.tar.gz" } ], "6.0.2": [ { "comment_text": "", "digests": { "md5": "33d323e521d2a6438ad99db96e0797c9", "sha256": "0c7d986f0242125033da9fc29789d647f3362a8f3146f135f1777f7c50e5b034" }, "downloads": -1, "filename": "rel2tree-6.0.2.tar.gz", "has_sig": false, "md5_digest": "33d323e521d2a6438ad99db96e0797c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4162, "upload_time": "2019-09-05T18:13:17", "url": "https://files.pythonhosted.org/packages/7b/65/637b4f83d439ba5547d710fb78702980ae3dffb7abfc5bb1c686766fdbc6/rel2tree-6.0.2.tar.gz" } ], "7.0.0": [ { "comment_text": "", "digests": { "md5": "fa45631adc0f80b9139d2a9821461172", "sha256": "b8cda0dae9eef738205756507d9592b25dac572efec2779ce7d0b7aee3cd13a1" }, "downloads": -1, "filename": "rel2tree-7.0.0.tar.gz", "has_sig": false, "md5_digest": "fa45631adc0f80b9139d2a9821461172", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4572, "upload_time": "2019-09-10T13:25:21", "url": "https://files.pythonhosted.org/packages/48/c0/19bcd30472f380ae1bcc60e724ec4138eacb4713f8c77e92f9c0ed1f7e34/rel2tree-7.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fa45631adc0f80b9139d2a9821461172", "sha256": "b8cda0dae9eef738205756507d9592b25dac572efec2779ce7d0b7aee3cd13a1" }, "downloads": -1, "filename": "rel2tree-7.0.0.tar.gz", "has_sig": false, "md5_digest": "fa45631adc0f80b9139d2a9821461172", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4572, "upload_time": "2019-09-10T13:25:21", "url": "https://files.pythonhosted.org/packages/48/c0/19bcd30472f380ae1bcc60e724ec4138eacb4713f8c77e92f9c0ed1f7e34/rel2tree-7.0.0.tar.gz" } ] }