{ "info": { "author": "Ryan Morshead", "author_email": "ryan.morshead@gmail.com", "bugtrack_url": null, "classifiers": [ "Intended Audience :: Developers", "Programming Language :: Python :: 3.6" ], "description": "[![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/rmorshea/purly/master?filepath=examples)\n\n\n# Purly\n\nControl the web with Python :snake:\n\n\n# Install\n\nTo install a stable version:\n\n```bash\npip install purly\n```\n\nTo install a dev version:\n\n> be sure to install [`npm`](https://www.npmjs.com/get-npm) first!\n\n```bash\n# clone the repository\ngit clone https://github.com/rmorshea/purly\ncd purly && bash scripts/build.sh && bash scripts/install.sh\n```\n\n# Getting Started\n\nRun the following snippet of code, and then navigate to http://127.0.0.1:8000/model/index.\n\n```python\nimport purly\n\n# Prepare your layout\npurly.state.Machine().run(debug=False)\nlayout = purly.Layout('ws://127.0.0.1:8000/model/stream')\n\n# create your HTML\ndiv = layout.html('div')\ndiv.style.update(height='20px', width='20px', background_color='coral')\n\n# add it to the layout\nlayout.children.append(div)\n\n# and sync it!\nlayout.sync()\n```\n\nNow your creation should have magically appeared in the browser page you opened!\n\n![div with some styling](https://raw.githubusercontent.com/rmorshea/purly/master/docs/getting-started-div.png)\n\n\n# Architecture (Not Final)\n\nPurly's fundamental goal is to give Python as much control of a webpage as possible, and do so in one incredibly simple package. There is one major problem that stands in the way of this goal - data synchronization. Purly's answer to this problem is its [model server](#model-server) which acts as a \"source of truth\" about the [state of a webpage](#model-specification) for any clients which connect to it and adhere to its [protocol](#communication-protocol).\n\n\n## Model Server\n\n![protocol](https://raw.githubusercontent.com/rmorshea/purly/master/docs/protocol/protocol.gif)\n\nPurly uses a web socket server to keep multiple concurrent clients in sync. The animation above shows 2 clients - a Python client pushing updates to a single Browser - however you could have more clients producing and / or consuming, model updates. Each client is associated with a single model (any JSON serializable object), however there can be multiple models that are stored on the server. Clients connect to a particular model by specifying its name in the socket route (e.g. `ws://host:port/model//stream`). Only clients that are connected to the same model communicate with each other via the server.\n\n\n## Model Specification\n\nWhile the Model Server supports any JSON serializable model, Purly, as a framework for controlling the web must:\n\n1. Communicate as fully as possible the structure of DOM elements and their various interactions.\n2. Send updates to DOM models over a network in short and easy to interpret packages:\n + Update messages must be small in size in order to reduce network traffic.\n\nTo accomplish the goals defined above we propose a flat DOM model:\n\n```python\nModel = {\n id: Element,\n # Maps a uniquely identifiable string to an Element.\n root: Element,\n # The id \"root\" should always indicate the outermost Element.\n ...\n}\n```\n\n```python\nElement = {\n tagName: string\n # Standard HTML tags like h1, table, div, etc.\n signature: string\n # The hash of this element attributes, and the hashes of its children.\n children: [\n string,\n # Any arbitrary string.\n {type: 'ref', 'ref': string},\n # An object where the key \"ref\" refers to the \"key\" attribute.\n ...\n ],\n attributes: {\n key: id,\n # The id that uniquely identifies this Element.\n parent_key: id\n # The unique id of this element's parent.\n attr: value,\n # Map any attribute name to any JSON serializable value.\n on: {\n # Specify an event callback with an attribute of the form \"on\".\n callback: uuid,\n # A unique identifier by which to refer to the callback function.\n keys: [...],\n # Details of the event to pass on to the callback.\n update: [...]\n # Any attributes that should be synced before the callback is triggered.\n }\n }\n}\n```\n\n\n### Purly Model Example\n\nThe following HTML\n\n```html\n
\n Make a selection:\n \n
\n```\n\nwould be communicated with the following Purly model:\n\n```python\n{\n root: {\n tag: 'div',\n elements: [\n 'Make a selection:'\n {'ref': 'abc123'},\n ]\n attributes: {\n 'key': 'root'\n }\n },\n abc123: {\n tag: 'input',\n elements: [],\n attributes: {\n 'key': 'abc123',\n 'type': 'text',\n },\n }\n}\n```\n\n\n## Communication Protocol\n\nThe Purly model server sends and receives JSON serializable arrays which contain objects in the form of a [Message](#message).\n\n```python\n[\n Message,\n # a dict conforming to the Message spec\n ...\n]\n```\n\n\n### Message\n\nThere are two types of messages - Updates and Signals - however both conform to the following format.\n\n```python\nMessage = {\n \"header\": {\n \"type\": \"signal\" or \"update\",\n # Message type (indicates the kind of content).\n \"version\": \"0.1\",\n # Message protocol version.\n }\n # Content which depends on the message type.\n \"content\": dict,\n}\n```\n\n\n#### Signals\n\n+ A Signal does not modify the state of the model server.\n+ Signal `content` is distributed unmodified to other model clients.\n\n\n#### Updates\n\n+ The `content` field specifies changes that will be merged into the model.\n+ Only the differences between the Update `content` and the model are distributed to other clients.\n+ Update merges are performed in a nested fashion\n\nIf the current state of the model is\n\n```python\n{\n 'a': {\n 'b': 1,\n 'c': 1,\n }\n}\n```\n\nand an update message\n\n```python\n{\n 'a': {\n 'c': 2\n }\n}\n```\n\nis received, the resulting model state is\n\n```python\n{\n 'a': {\n 'b': 1,\n 'c': 2,\n }\n}\n```\n\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://github.com/rmorshea/purly", "keywords": "interactive,widgets,DOM,synchronization,React", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "purly", "package_url": "https://pypi.org/project/purly/", "platform": "Linux", "project_url": "https://pypi.org/project/purly/", "project_urls": { "Homepage": "https://github.com/rmorshea/purly" }, "release_url": "https://pypi.org/project/purly/0.2.0/", "requires_dist": [ "sanic", "sanic-cors", "asyncio", "websocket-client", "websockets (==5.0)", "spectate (>=0.2.1)" ], "requires_python": "", "summary": "Control the web with Python", "version": "0.2.0" }, "last_serial": 4356985, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "f369c8a42f8031951e3200f7fb8fa6f5", "sha256": "c88dc02d7079d6f99f7fc25a53c1174b0b703f99900090596b42b4778a43262e" }, "downloads": -1, "filename": "purly-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "f369c8a42f8031951e3200f7fb8fa6f5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 1809, "upload_time": "2018-06-05T20:24:17", "url": "https://files.pythonhosted.org/packages/2c/f5/8b1f7ce52aa327acbf7cd4a1f980b31ee1aef6c586b9fc92805e47ac51df/purly-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b88d3efba7912977c93c2f5d6022d00f", "sha256": "090584917606a40994c0e926255eae143ef5ff845a050269b2381e49f1c77f01" }, "downloads": -1, "filename": "purly-0.0.1.tar.gz", "has_sig": false, "md5_digest": "b88d3efba7912977c93c2f5d6022d00f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 747, "upload_time": "2018-06-05T20:24:18", "url": "https://files.pythonhosted.org/packages/ae/74/86a88086624b8357a46b242c04c156635c0fd4f28c6dbe5f962a9a472473/purly-0.0.1.tar.gz" } ], "0.1.0": [ { "comment_text": "", "digests": { "md5": "89868434412fad1322c02925b0d5eb42", "sha256": "8b9f97b0a1f9ed0bb31966004d3c6119938864c03cc4c292d99a46ed551bcefa" }, "downloads": -1, "filename": "purly-0.1.0-py3.6.egg", "has_sig": false, "md5_digest": "89868434412fad1322c02925b0d5eb42", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": null, "size": 473422, "upload_time": "2018-08-17T05:47:53", "url": "https://files.pythonhosted.org/packages/8d/46/a0ee07a84334eaea931a06ba547f98e9fce8031df40cf035e939ba9f8af9/purly-0.1.0-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "de4dd5cd04f120390f152e1588b478aa", "sha256": "7353c077406e7c9f2548b007926c2874ce084391250eb52f21c171eb4f024f70" }, "downloads": -1, "filename": "purly-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "de4dd5cd04f120390f152e1588b478aa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 465710, "upload_time": "2018-08-17T05:47:50", "url": "https://files.pythonhosted.org/packages/20/09/359c448cbd6664bf25424abdfbdc719e7b9fd76fe670327dbf7faa4362fd/purly-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fe7d9afc953e87ca069ca412f85f55e8", "sha256": "0833f36d1abbbe475362acd57e19602f5d69ec3acb79b6fa02420a3180ee8120" }, "downloads": -1, "filename": "purly-0.1.0.tar.gz", "has_sig": false, "md5_digest": "fe7d9afc953e87ca069ca412f85f55e8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 463373, "upload_time": "2018-08-17T05:47:56", "url": "https://files.pythonhosted.org/packages/06/30/24eefdbdf93298102859fbdd11090f0fab6c1c137467da44633006abeb81/purly-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "dd3fc308840fdfad978904524bc75e6b", "sha256": "0538a121a023d03bf1caac98089bb9edeab28166156ea1fb9582fa4902cfd4a1" }, "downloads": -1, "filename": "purly-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "dd3fc308840fdfad978904524bc75e6b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166181, "upload_time": "2018-10-05T03:42:22", "url": "https://files.pythonhosted.org/packages/e5/fb/63526ef60dc6a0cf61dbde98b66101d5a373cb7366b795d565c8a971edfc/purly-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "99f7085df9b62705f42d3d7388cd2a34", "sha256": "6d5eb0df4b501fa151a2870f4b66db466b1ad6c510473edf24df50d39c948b35" }, "downloads": -1, "filename": "purly-0.2.0.tar.gz", "has_sig": false, "md5_digest": "99f7085df9b62705f42d3d7388cd2a34", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151251, "upload_time": "2018-10-05T03:42:27", "url": "https://files.pythonhosted.org/packages/e4/db/a235558f160bf50a38897f70d388746348e5e37a49bb5edb9c836a526ade/purly-0.2.0.tar.gz" } ], "0.2.1.dev0": [ { "comment_text": "", "digests": { "md5": "27cc8b2c486a6ea45173d17c1ee23373", "sha256": "c370e03227fc26b3f3be8ee16fb9e3262340eaf250463467d645da91445e26fc" }, "downloads": -1, "filename": "purly-0.2.1.dev0-py3-none-any.whl", "has_sig": false, "md5_digest": "27cc8b2c486a6ea45173d17c1ee23373", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166213, "upload_time": "2018-10-05T04:21:18", "url": "https://files.pythonhosted.org/packages/4d/3f/982561e078bedc2e0ce9fe5a1155209d18b37cf4acb24b338507285f9b17/purly-0.2.1.dev0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a4e907bdd3bbb78a596b71d8e30480c6", "sha256": "5cea2703cdce2ba3f380a919da787b6702e9c520c6f88aae899d59aaad5f57b2" }, "downloads": -1, "filename": "purly-0.2.1.dev0.tar.gz", "has_sig": false, "md5_digest": "a4e907bdd3bbb78a596b71d8e30480c6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151244, "upload_time": "2018-10-05T04:21:22", "url": "https://files.pythonhosted.org/packages/4c/6d/cf1e1f34cf123a819d50047c8e716f8fde02ef996399be42b5c33a6b8987/purly-0.2.1.dev0.tar.gz" } ], "0.2.1.dev1": [ { "comment_text": "", "digests": { "md5": "5218cc8101024e5576593ffb4712b563", "sha256": "a93ae873d3ef7955cd8054af20be3cb6c4fc09e1dc6bdb455396fc2be4bc7d5b" }, "downloads": -1, "filename": "purly-0.2.1.dev1-py3-none-any.whl", "has_sig": false, "md5_digest": "5218cc8101024e5576593ffb4712b563", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166213, "upload_time": "2018-10-08T03:43:09", "url": "https://files.pythonhosted.org/packages/59/86/2434f2cd2965cb8155ea7eac9ebc60986166f1f46d40a2cf130d3ba0a2fc/purly-0.2.1.dev1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ad2bb7a231d4b8ff9fc5ea58c4c8cb20", "sha256": "b99df2d7f814cb27e1ad29983f72d9544b978a5bc9f040ee508d939ec62eb5cb" }, "downloads": -1, "filename": "purly-0.2.1.dev1.tar.gz", "has_sig": false, "md5_digest": "ad2bb7a231d4b8ff9fc5ea58c4c8cb20", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151250, "upload_time": "2018-10-08T03:43:18", "url": "https://files.pythonhosted.org/packages/ca/42/f434590a1f38be4f018c9ac77abdd76078d49206c9b350015e925c86cbbf/purly-0.2.1.dev1.tar.gz" } ], "0.2.1.dev2": [ { "comment_text": "", "digests": { "md5": "8b1bbe81971b33d19a220e5a3d340e8c", "sha256": "10bd7b1a9789462216088fa030b1647ade28b7b4b6fdf698e3c25af57d489704" }, "downloads": -1, "filename": "purly-0.2.1.dev2-py3-none-any.whl", "has_sig": false, "md5_digest": "8b1bbe81971b33d19a220e5a3d340e8c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166290, "upload_time": "2018-10-08T03:52:57", "url": "https://files.pythonhosted.org/packages/1f/c8/68d076903feb53ec5ba0b5a4857d34d6a215734cbe04e25ab5d70d2ce90d/purly-0.2.1.dev2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1ecfa55b4956d3bd40049dd7afaca327", "sha256": "53a4eb3f9b9d24b24ad83dcd768fd2ee8dc9cc163e704c640b0ce473e5096503" }, "downloads": -1, "filename": "purly-0.2.1.dev2.tar.gz", "has_sig": false, "md5_digest": "1ecfa55b4956d3bd40049dd7afaca327", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151280, "upload_time": "2018-10-08T03:53:02", "url": "https://files.pythonhosted.org/packages/08/ed/af353c6a0f343217cd7ae03467dfbf720b9ac46a7202f299d464ba8f3a29/purly-0.2.1.dev2.tar.gz" } ], "0.2.1.dev3": [ { "comment_text": "", "digests": { "md5": "edcf45e3d547534e31a2ff8262fb3cee", "sha256": "ef7325053fef8e1e5c1302c630ea2d25f29c1d55ebfb036380bed1e30ef5f15a" }, "downloads": -1, "filename": "purly-0.2.1.dev3-py3-none-any.whl", "has_sig": false, "md5_digest": "edcf45e3d547534e31a2ff8262fb3cee", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166288, "upload_time": "2018-10-09T08:09:59", "url": "https://files.pythonhosted.org/packages/f2/35/cb8b0fb3fa2a7048d959de84aabef76a51f7dfaa1ba5a807f717069e7b07/purly-0.2.1.dev3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "98ccc27938dd06b79580fe99d4034831", "sha256": "0356183dae883fd42b89c70b943f6b94e273865a212ed7c8121ba6aac2dd2a5a" }, "downloads": -1, "filename": "purly-0.2.1.dev3.tar.gz", "has_sig": false, "md5_digest": "98ccc27938dd06b79580fe99d4034831", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151305, "upload_time": "2018-10-09T08:10:03", "url": "https://files.pythonhosted.org/packages/65/82/13997d7e7e93c2feda83d6bae97670c550ad782d9b6c948612d86da11169/purly-0.2.1.dev3.tar.gz" } ], "0.2.1.dev4": [ { "comment_text": "", "digests": { "md5": "4f0fdb5e33da0b46a73065659ded4ee5", "sha256": "91a4ef08338c76eb6922de042ee3257138abc7caf5001036f856a91f1b2ef69f" }, "downloads": -1, "filename": "purly-0.2.1.dev4-py3-none-any.whl", "has_sig": false, "md5_digest": "4f0fdb5e33da0b46a73065659ded4ee5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166289, "upload_time": "2018-10-09T18:13:14", "url": "https://files.pythonhosted.org/packages/5a/f6/2ec749e1974ca417d6e68d49d28797e30a9b9cff5772a32d6b915c621ffd/purly-0.2.1.dev4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "306b938439cf825d50cb727c45482fa3", "sha256": "5c9a95dd89bb9cac47c19d234f06f30eda07ac4223ad1ceca0c0fd0da85a1969" }, "downloads": -1, "filename": "purly-0.2.1.dev4.tar.gz", "has_sig": false, "md5_digest": "306b938439cf825d50cb727c45482fa3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151306, "upload_time": "2018-10-09T18:13:20", "url": "https://files.pythonhosted.org/packages/93/2b/1edb8ef677c9bf7930c9cea52cfbc242c485863018a271d461c63209879c/purly-0.2.1.dev4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "dd3fc308840fdfad978904524bc75e6b", "sha256": "0538a121a023d03bf1caac98089bb9edeab28166156ea1fb9582fa4902cfd4a1" }, "downloads": -1, "filename": "purly-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "dd3fc308840fdfad978904524bc75e6b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 2166181, "upload_time": "2018-10-05T03:42:22", "url": "https://files.pythonhosted.org/packages/e5/fb/63526ef60dc6a0cf61dbde98b66101d5a373cb7366b795d565c8a971edfc/purly-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "99f7085df9b62705f42d3d7388cd2a34", "sha256": "6d5eb0df4b501fa151a2870f4b66db466b1ad6c510473edf24df50d39c948b35" }, "downloads": -1, "filename": "purly-0.2.0.tar.gz", "has_sig": false, "md5_digest": "99f7085df9b62705f42d3d7388cd2a34", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 2151251, "upload_time": "2018-10-05T03:42:27", "url": "https://files.pythonhosted.org/packages/e4/db/a235558f160bf50a38897f70d388746348e5e37a49bb5edb9c836a526ade/purly-0.2.0.tar.gz" } ] }