{ "info": { "author": "Tyler F Cloutier", "author_email": "cloutiertyler@aol.com", "bugtrack_url": null, "classifiers": [ "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content" ], "description": "# RibbonGraph\nA Declarative Graph API Django App\n\nRibbonGraph is a Django app which provides a declarative permission layer to a\nNeo4j database. It is perfect for building social networking applications.\n\nThe RibbonGraph philosophy is that client applications should be able to do whatever\nthey want to the graph database, but ***only if they are allowed to***.\n\nAll you need to do is specify what *is* and *is not* allowed.\n\n> #### Why the name RibbonGraph? \n\n> Well the idea is that RibbonGraph wraps all the potentially very complicated logic of social network into a neat little package.\n\n> #### Why did you create RibbonGraph?\n\n> I created this package because I was fed up with complex endpoints, and wanted to find a better way to build my app, [Hungrosity](https://itunes.apple.com/us/app/hungrosity/id917006014).\n\nUser Guide\n----------\nRibbonGraph will allow you to simplify all of your API endpoints down to just a few. The example below demonstrates the power of RibbonGraph. Here we are using django-rest-framework's APIView class to create a NodeView.\n\n class NodeView(APIView):\n authentication_classes = (TokenAuthentication, )\n permission_classes = (IsAuthenticated, )\n\n def get(self, request, node_type=None, id=None):\n \"\"\"\n Returns a json object representing the tree of graph nodes rooted at the node\n with identifier `id` or if no `id` is specified a list of trees rooted at the\n nodes that meet the query constraints.\n \"\"\"\n user = request.user\n api = GraphAPI(settings.NEO4J_URL, models=model_list)\n query_dict = get_query_dict_from_params(request.query_params)\n if id:\n id = int(id)\n response_data = api.request_subgraph_at_node(user.id, query_dict.get('include', None), id)\n else:\n response_data = api.query_for_subgraphs(user.id, query_dict, node_type)\n return Response(response_data)\n\nThe GET endpoint defined above let's the client query for arbirary subtrees in the graph with a single request.\nNow there is no need for multiple round trips to different API endpoints. No complicated APIs or need for batching requests.\n\nFor example when a user logs in you can request all of the user's information, plus all of their friends' information and even their friends' friends' information. The granularity of the request is down to the field level.\n\nLet's look at what that request might look like:\n\n https://my-app.com/nodes/23432?include=first_name,last_name,friends.include(first_name,last_name),received_friend_requests.include(sender,receiver)\n\nWith is one call we are able to get all the friends and friend requests of user `23432` with a single request. Best of all we can easily add or remove fields extremely easily. This makes debugging and development significantly faster.\n\nWe can create a similar POST endpoint that allows clients to create nodes in the graph. An update in this case is specified as JSON in the request body.\n\n def post(self, request, node_type):\n \"\"\"\n Applies the tree update rooted at the node being created.\n \"\"\"\n user = request.user\n api = GraphAPI(settings.NEO4J_URL, models=model_list)\n create_dict = request.data\n response_data = api.update_subgraph_at_node(user.id, 'create', create_dict, node_type=node_type)\n return Response(response_data)\n\nThe syntax for updates is very similar to the syntax for GET requests, with some small differences. Here's what sending a friend request might look like.\n\nurl:\n\n https://my-app.com/nodes/FriendRequest\n\nbody:\n\n {\n \"sender\":{\n \"attach\":{\n \"id\":23432\n }\n },\n \"receiver\":{\n \"attach\":{\n \"id\":83472\n }\n },\n \"receiver_has_seen\":false\n }\n \nHere we are saying that we'd like to create an object of type FriendRequest and we'd like to attach that to two users, the sender and the receiver.\n\nYou may have noticed something is missing, however. That something is permissions.\n\nPermissions\n-----------\n\nPermissions are what define your GraphAPI. The philosophy of RibbonGraph is that clients should be able to make any change to the shared graph that they want, but only changes ***they are allowed to make***.\n\nRibbonGraph allows you to declaratively specify what clients are and are not allowed to do to the graph. This can be anything from ensure that all `FriendRequests` have a `receiver_has_seen` field, to ensuring that only receivers of `FriendRequest`s can make a friendship between themselves and the sender.\n\nThe sky is the limit really.\n\n class Friendship(Relationship):\n def assert_allows_add_edge(self, graph, actor_id, node_id, id_to_add, tx):\n \"\"\"\n \n The actor is the receiver of the request.\n\n Allow the receiver of the request to add the sender of the request to\n its friends or add itself to the sender's friends.\n \n \"\"\"\n user_id = User.get_user_id(graph, node_id, tx)\n user_id_to_add = User.get_user_id(graph, id_to_add, tx)\n\n # Allow the receiver to add the sender to the receiver's friends.\n if actor_id == user_id_to_add:\n if not User.user_sent_friend_request_to_user(graph, user_id, actor_id, tx):\n raise PermissionDenied()\n return\n\n # Allow the sender to add the receiver to the sender's friends.\n if actor_id == user_id:\n if not User.user_sent_friend_request_to_user(graph, user_id_to_add, actor_id, tx):\n raise PermissionDenied()\n return\n\n raise PermissionDenied()\n\nAbove we allow user `A` to add themselves to user `B`'s list of friends if and only if user `B` sent a friend request to user `A`. \n\nAnd just like that we have a social network.\n\n\nRequirements\n------------\nAs of now this app is meant for use with the [djangorestframework](http://www.django-rest-framework.org) and it uses that projects base class APIException for all GraphAPIErrors.\n\nQuick start\n-----------\n\n1. Run\n\n pip install ribbon-graph\n2. Add \"ribbon\" to your INSTALLED_APPS setting like this:\n\n INSTALLED_APPS = [\n ...\n 'ribbon',\n ]", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/TheArtOfEngineering/RibbonGraph", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "ribbon-graph", "package_url": "https://pypi.org/project/ribbon-graph/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/ribbon-graph/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/TheArtOfEngineering/RibbonGraph" }, "release_url": "https://pypi.org/project/ribbon-graph/0.1.1/", "requires_dist": null, "requires_python": null, "summary": "A Declarative Graph API Django App", "version": "0.1.1" }, "last_serial": 2057046, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "bae9d9983797b19aa12a2f55b3f722c1", "sha256": "314ebee54f8c15b12ce9e3bdeda12e97a0e6ffd498d4b812b163d15288563ba7" }, "downloads": -1, "filename": "ribbon_graph-0.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "bae9d9983797b19aa12a2f55b3f722c1", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 14097, "upload_time": "2016-04-11T00:24:10", "url": "https://files.pythonhosted.org/packages/78/b5/cac98b26fb39035de94cd8bf92b9e2e173d972a1c13cb48206d2baa7626e/ribbon_graph-0.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b9ac4a1067a067d57166508a3de01500", "sha256": "45ea2820ec27bba3ed3fbde5b0f903c2bedf5c16a012b7ff1078fcd1e808038d" }, "downloads": -1, "filename": "ribbon-graph-0.0.1.tar.gz", "has_sig": false, "md5_digest": "b9ac4a1067a067d57166508a3de01500", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11912, "upload_time": "2016-04-11T00:24:05", "url": "https://files.pythonhosted.org/packages/95/ff/adc636cda8705efc2df844dcee5f7cb3721e23c5f58ac684e60b8821d8ac/ribbon-graph-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "6429cadcbb0e5a8844d8c81b0dbb5eaa", "sha256": "d8e5435dad70d8f518d350d9d0e062ed4258dafa98e8a87e7c870ddac2bdee20" }, "downloads": -1, "filename": "ribbon_graph-0.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "6429cadcbb0e5a8844d8c81b0dbb5eaa", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 14245, "upload_time": "2016-04-11T00:25:16", "url": "https://files.pythonhosted.org/packages/58/f8/c1fa6f83c930f8a8dd542572af427b69330b1aec04a750e972f8f4d1b7bb/ribbon_graph-0.0.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "49d94711b19e8233999f83c14041b732", "sha256": "40f18393854b8698971bb768a6fef13450de6d94f49e35ed866da178958692fe" }, "downloads": -1, "filename": "ribbon-graph-0.0.2.tar.gz", "has_sig": false, "md5_digest": "49d94711b19e8233999f83c14041b732", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12063, "upload_time": "2016-04-11T00:25:09", "url": "https://files.pythonhosted.org/packages/03/b4/90de332686eff0fe3aea81394f009875ec9af4633f9bb9a5a03d4b62d79c/ribbon-graph-0.0.2.tar.gz" } ], "0.1": [ { "comment_text": "", "digests": { "md5": "23de104025d03992d3292ba0a061f28d", "sha256": "6ae41f14845584518c3e9705374b6343bea147275181db32238a761e86aa5e50" }, "downloads": -1, "filename": "ribbon_graph-0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "23de104025d03992d3292ba0a061f28d", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 14038, "upload_time": "2016-04-10T23:58:05", "url": "https://files.pythonhosted.org/packages/5d/c7/9e7dd68d4190ca698a7a04cd9271a5e07f31348b8fbe2a9b7c26ce674034/ribbon_graph-0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "cb69bca5998b93a561d606523d52f4ec", "sha256": "d68a0d9e1668fe8af9574dcb03f141da92986c4c82f1b2807b2208b9a9702135" }, "downloads": -1, "filename": "ribbon-graph-0.1.tar.gz", "has_sig": false, "md5_digest": "cb69bca5998b93a561d606523d52f4ec", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11846, "upload_time": "2016-04-10T23:31:14", "url": "https://files.pythonhosted.org/packages/24/22/35613c5d3fbdec89559294049464f7d2dc1fbcff685a18d0ed4f89c8190a/ribbon-graph-0.1.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "61a663292d33459b88094867acdd8590", "sha256": "d9213e30fe9d90d211b6bcac941a8177a0c5d38d5872ec7affdbba51f0ce232d" }, "downloads": -1, "filename": "ribbon_graph-0.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "61a663292d33459b88094867acdd8590", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 17940, "upload_time": "2016-04-11T04:16:12", "url": "https://files.pythonhosted.org/packages/59/63/a6821244397cbef98016ac375279d122e664a7f56ff42951729eb0b89783/ribbon_graph-0.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "21a21139e8e497170354f38b3c68adec", "sha256": "00085726bfffb7c8024972133eb24c7bad85022f32610573598886d756f1a92b" }, "downloads": -1, "filename": "ribbon-graph-0.1.1.tar.gz", "has_sig": false, "md5_digest": "21a21139e8e497170354f38b3c68adec", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15493, "upload_time": "2016-04-11T04:16:02", "url": "https://files.pythonhosted.org/packages/94/c2/896b4e116850314dafacd2c50e0ffc2969ad68b0b9f51f11edc5eea0c237/ribbon-graph-0.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "61a663292d33459b88094867acdd8590", "sha256": "d9213e30fe9d90d211b6bcac941a8177a0c5d38d5872ec7affdbba51f0ce232d" }, "downloads": -1, "filename": "ribbon_graph-0.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "61a663292d33459b88094867acdd8590", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 17940, "upload_time": "2016-04-11T04:16:12", "url": "https://files.pythonhosted.org/packages/59/63/a6821244397cbef98016ac375279d122e664a7f56ff42951729eb0b89783/ribbon_graph-0.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "21a21139e8e497170354f38b3c68adec", "sha256": "00085726bfffb7c8024972133eb24c7bad85022f32610573598886d756f1a92b" }, "downloads": -1, "filename": "ribbon-graph-0.1.1.tar.gz", "has_sig": false, "md5_digest": "21a21139e8e497170354f38b3c68adec", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15493, "upload_time": "2016-04-11T04:16:02", "url": "https://files.pythonhosted.org/packages/94/c2/896b4e116850314dafacd2c50e0ffc2969ad68b0b9f51f11edc5eea0c237/ribbon-graph-0.1.1.tar.gz" } ] }