{ "info": { "author": "Louis T. Getterman IV", "author_email": "Thad.Getterman@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Programming Language :: Python :: 3" ], "description": "# Polycephaly\n\nPolycephaly is a Python module that allows you to easily create programs that are capable of parallel operations at the I/O and CPU levels:\n\n - Email-like syntax for inter-process communications, without external dependencies such as SQLite or Redis.\n - Message routing (e.g. between processes operating under Polycephaly, and other platforms such as Unix Domain Socketfile, D-Bus, MQTT, USB, et al.)\n - Message filters for assigning callbacks to receive messages on different routes.\n - JSON encoding and parsing for external messaging (e.g. Unix Domain Socketfile or MQTT).\n - FIFO ~~or Priority message~~ queuing.\n - Priority message queuing is still under development since [Priority Queues only work in Threaded mode](https://stackoverflow.com/questions/25324560/strange-queue-priorityqueue-behaviour-with-multiprocessing-in-python-2-7-6).\n\n[![Message relay example - short](https://img.youtube.com/vi/jKTcnDsyQcA/0.jpg)](https://www.youtube.com/watch?v=jKTcnDsyQcA)\n\n## Overview\n\nOriginally, Polycephaly was created as a shared framework for building a Linux-based, embedded system for [Robot Operating System](https://www.ros.org/)-based robotics with a large touchscreen monitor with Python. This framework is shared between 2 separate processes running in tandem as a server and a client:\n\n 1. **System service**\n - Shared messaging library (e.g. Unix Domain Socketfile Server).\n - System and network management:\n - Persistent Internet connection for remote monitoring and management.\n - Applying system updates with opportunistic downtime (e.g. rebooting for a UEFI, Grub, and/or Kernel update outside of peak usage and business hours).\n - Software and hardware communications, including but not limited to:\n - MQTT\n - IPC\n - Unix domain socket\n - Message queue\n - Pipe\n - USB\n - TTL (via USB)\n 2. **Touchscreen application**\n - Shared messaging library (e.g. Unix Domain Socketfile Client).\n - Administrative functions and hardware diagnostics.\n - Interfacing to mobile devices via QR Codes.\n\n---\n\n*Please note: this module should not be considered production-ready, and many improvements are still underway.*\n\n*Since friends have indicated that they could use this for some of their projects, I'm releasing this far earlier than I probably should. If you find an error or have a suggestion, please consider opening a bug report, or better yet, submitting a pull request.*\n\n---\n\n## Usage\n\n### Get\n\nTo download Polycephaly:\n\nInstall from [PyPI](https://pypi.org/project/polycephaly) via `pip`:\n\n```sh\n$ pip install polycephaly\n```\n\nor\n\nInstall from this [repository](https://gitlab.com/ltgiv/polycephaly) via `pip`:\n\n```sh\n$ pip install git+ssh://git@gitlab.com/ltgiv/polycephaly.git\n```\n\nor\n\nClone this [repository](https://gitlab.com/ltgiv/polycephaly):\n\n```sh\n$ git clone https://gitlab.com/ltgiv/polycephaly.git\n```\n\n### Grok\n\nPolycephaly is 2 components that are the same derived class for consistency. Thus, every process is interchangeable.\n\n#### Components\n\nAt the very least, there is the main process, and then there are sub-processes.\n\n 1. **Main process**\nSince many TUI/GUI frameworks (e.g. [Urwid](http://urwid.org/), [curses](https://docs.python.org/3/howto/curses.html), [Kivy](https://en.wikipedia.org/wiki/Kivy_%28framework%29), [pyglet](https://en.wikipedia.org/wiki/Pyglet), [PyQt](https://en.wikipedia.org/wiki/PyQt), [Tkinter](https://en.wikipedia.org/wiki/Tkinter), et al.) work better as (or have to be) the main process, this easily allows for that. If this process ends, all of the sub-processes are shutdown.\n\n 2. **Sub-processes**\nThese answer to the main process. When setting up your application, each sub-process has a mode that can be toggled between threaded or forked, with the former being the default, and the latter better used for CPU-intensive tasks (e.g. training a Machine Learning model) or where required (e.g. [rospy](http://wiki.ros.org/rospy) requires this.)\n\n---\n\n*For the sub-processes whose operating modes are set to forked, they will operate independently, under a separate Python process, and cannot share existing objects (e.g. dictionaries) like they can if they were threaded. This is where the email-like communications of Polycephaly shine, as it allows you to pass serializable objects.*\n\n*If you're wanting to share an object such as a Network or Database connection, you'd simply devote a sub-process to this task, and then use Polycephaly's communications from other processes for carrying out their requests, including the ability to wait for replies.*\n\n---\n\n#### Methods\n\nThe lifetime of a process has 3 stages:\n\n 1. `birth( self )` - You can think of this as a constructor. This carries out preliminary actions (e.g. initializing hardware or connecting to a database) before looping the method, `life()`.\n - `args` - a tuple of arguments that are passed from the application's *build* side to the process.\n - `kwargs` - a dictionary of keyword arguments that are passed from the application's *build* side to the process.\n 2. `life( self )` - This is the entirety of the process. When building a Polycephaly-based application, you can adjust a global and/or local frequency value, which dictates how often this method should be run. For example, if you set this value to 30, Polycephaly would attempt to run this method 30 times per second with a sleep time after each run of `life()` defined by `time.sleep( 1 / 30 )`\n 3. `death( self )` - You can think of this as a destructor. This carries out the cleanup portion of a process (e.g. finalizing database transactions and then closing the database connection) before notifying the main process that it has reached its end of life, and has fully shutdown.\n\nMany helper methods are inherited from the parent class for use in each process definition. The [API documentation](https://ltgiv.gitlab.io/polycephaly) provides more information, but some of the common ones to pay attention to, are:\n\n - `frequency( i=None )` - Without an argument provided, this will return the current integer for the local process' frequency. With an argument, this will write an integer to be used for a frequency. An example for increasing and decreasing frequency if an instance boolean is toggled:\n\n ```python\n if self.stayAlert and self.frequency() < 60:\n self.frequency( 60 )\n\n elif self.frequency() != 30:\n self.frequency( 30 )\n ```\n\n - `mailman()` - With no arguments provided, this checks for new messages destined for the process on the internal message bus from the main process or a sub-process.\n - If a filter matches a message, the callback is then executed. This is normally a blocking event, but callbacks can be spun off into threaded or forked processes if so desired.\n - Only one message is read from the process' queue per run.\n - Only run once per loop of `life()`.\n - This can be easily extended to cover other message queues and routes, such as checking for (JSON-based) messages received from MQTT, XMPP, or USB, for example.\n\n - `die()` - Used by a process to shut itself down, and make its way towards `death()` as the final step.\n\n During the *build* phase of your application, a poison pill is generated, which is simply a UUID saved as a string. A message filter is then automatically added for each process, that looks for this value in the subject line on the internal route. When a process receives this value from the main process or a sub-process, the receiving process will run its `die()` method.\n\n - `ebrake( reason=None )` - As the name implies, this *Emergency Brake* method allows any process within the application to send a request to the main process, requesting an immediate shutdown of the application, and allows for an optional reason that will be shown as a part of the shutdown message.\n\n - `send()` - Send a message from one process to another. An example:\n\n ```python\n self.send(\n recipient = \"main\",\n subject = \"salutation\",\n body = \"Hello, World!\"\n )\n ```\n\n - `waitForReply()` - Blocking event with an option for timing out, that will wait for a response from a recipient, before proceeding. An example:\n\n ```python\n message = self.send(\n recipient = \"main\",\n subject = \"salutation\",\n body = \"Hello, World!\"\n )\n\n reply = self.waitForReply( message, timeout=10 )\n ```\n\n### Go!\n\nThis is a high-level overview of the [\"Hello, World!\" example](https://gitlab.com/ltgiv/polycephaly/tree/master/examples/helloWorld). The [examples directory](https://gitlab.com/ltgiv/polycephaly/tree/master/examples) is usually the best place to start from, for fully functioning code:\n\n#### Build\n\n**`launch.py`**:\n\n```python\n#!/usr/bin/env python -u\n# -*- coding: utf-8 -*-\n\nclass Application( polycephaly.Application ):\n\n def build( self ):\n\n # Update global frequency\n self.globalFrequency( 15 ) # Run fifteen times per second.\n\n # Add process : Hello, World!\n self.addProcess(\n\n processes.helloWorld, # If the default class name of `Process` is used, it doesn't need to be specified here.\n\n # Arguments to pass through to the process.\n 'Arg1',\n 'Arg2',\n\n # Keyword arguments to pass through to the process.\n abc = 123,\n xyz = 789,\n\n # Process Parameters\n name = 'Hello', # Override the default name of `helloWorld` with a shorter name of `hello`.\n mode = 'Thread', # Run the process as a thread.\n frequency = 1 / 5, # Update the local frequency to run once every 5 seconds.\n autostart = True, # This is default behavior, with the alternative being to setup a process, and then start it at a later time.\n boundShutdown = False, # Run independently without binding to main process.\n\n )\n\n pass # END METHOD : Build\n\n pass # END CLASS : Application\n\nif __name__ == '__main__':\n\n logger.notice( \"Start : 'Hello, World!'.\" )\n\n Application(\n\n # Add process : Main\n processes.main,\n name = 'Main', # Set the name of the process that we refer to, or specify an added process as main.\n ppill = 'STOP!', # Case-sensitive poison pill.\n queueSize = 25, # Maximum number of messages to keep in each queue.\n queueType = 'FIFO', # FIFO or Priority message queue.\n frequency = 5, # Update the local frequency to run five times per second.\n forceStop = False, # Allow the process to ignore repeated shutdown requests.\n threadsTimeout = 30, # Application will wait on threads for this long.\n\n ).run()\n\n logger.notice( \"Stop : 'Hello, World!'.\" )\n\n pass # END MAIN\n```\n\n**`helloWorld.py`**\n\n```python\n#!/usr/bin/env python -u\n# -*- coding: utf-8 -*-\n\nclass Process( polycephaly.Process ):\n\n def life( self ):\n\n # Send a message to the Main process.\n message = self.send(\n\n # Message parameters\n recipient = 'main',\n subject = 'salutation',\n body = 'Hello, World!',\n\n # Extra message headers\n args = self.args,\n kwargs = self.kwargs,\n\n )\n logger.debug( f\"'{ self.name }' sent a message to '{ message[ 'recipient' ] }':\\n{ pf( message ) }\" )\n\n # Wait for reply from the Main process.\n reply = self.waitForReply( message, timeout=10 )\n logger.debug( f\"'{ self.name }' received a reply from '{ reply.get( 'sender' ) }':\\n{ pf( reply ) }\" )\n\n # Check for new messages, and run appropriate callbacks.\n self.mailman()\n\n pass # END METHOD : Life\n\n pass # END CLASS : PROCESS : Hello, World!\n```\n\n**`main.py`**\n\n```python\n#!/usr/bin/env python -u\n# -*- coding: utf-8 -*-\n\nclass Process( polycephaly.Process ):\n\n # This is a callback method that replies back to a message.\n def salutation( self, message ):\n\n logger.debug( f\"'{ self.name }' received a salutation message from '{ message[ 'sender' ] }':\\n{ pf( message ) }\" )\n\n # Respond to a received message.\n reply = self.reply(\n\n # Message parameters\n message,\n body = \"Hi, thanks for writing.\",\n\n # Extra message headers\n acme = 123,\n\n )\n logger.debug( f\"'{ self.name }' sent a reply to '{ reply[ 'recipient' ] }':\\n{ pf( reply ) }\" )\n\n pass # END CALLBACK : Salutation\n\n def birth( self ):\n\n # Add a message filter for case-insensitive matching of \"salutation\" in the subject line, with self.salutation() set as the callback method.\n self.addFilter(\n subject = r'(?i)^SALUTATION$',\n callback = self.salutation,\n )\n\n pass # END METHOD : Birth\n\n pass # END CLASS : PROCESS : Main\n```\n\n#### Run\n\nRunning this application is as simple as `~/helloWorld/launch.py`\n\nNow, every 5-6 seconds, you'll see output on your console:\n\n```python\n[1970-01-01 00:00:00.00] DEBUG: processes.helloWorld: 'hello' sent a message to 'main':\n{\n 'args': ('Arg1', 'Arg2'),\n 'body': 'Hello, World!',\n 'kwargs': {'abc': 123, 'xyz': 789},\n 'messageid': '9da03bcf-af3d-49ad-a9d4-17617c974de6',\n 'recipient': 'main',\n 'sender': 'hello',\n 'subject': 'salutation',\n 'threadid': '05cc7e85-f291-4c63-af34-6d47b8fc2594',\n 'threadindex': 1,\n 'time': 123456789.0000\n}\n\n[1970-01-01 00:00:00.05] DEBUG: processes.main: 'main' received a salutation message from 'hello':\n{\n 'args': ('Arg1', 'Arg2'),\n 'body': 'Hello, World!',\n 'kwargs': {'abc': 123, 'xyz': 789},\n 'messageid': '52c7cca0-0f0a-4791-a1d8-7de96d5b2c68',\n 'recipient': 'main',\n 'sender': 'hello',\n 'subject': 'salutation',\n 'threadid': '05cc7e85-f291-4c63-af34-6d47b8fc2594',\n 'threadindex': 1,\n 'time': 123456789.0005\n}\n\n[1970-01-01 00:00:00.10] DEBUG: processes.main: 'main' sent a reply to 'hello':\n{\n 'acme': 123,\n 'args': ('Arg1', 'Arg2'),\n 'body': 'Hi, thanks for writing.',\n 'kwargs': {'abc': 123, 'xyz': 789},\n 'messageid': '270c8371-0467-4648-9630-5376e33ababa',\n 'recipient': 'hello',\n 'sender': 'main',\n 'subject': 'reply',\n 'threadid': '05cc7e85-f291-4c63-af34-6d47b8fc2594',\n 'threadindex': 2,\n 'time': 123456789.0010\n}\n\n[1970-01-01 00:00:00.15] DEBUG: processes.helloWorld: 'hello' received a reply from 'main':\n{\n 'acme': 123,\n 'args': ('Arg1', 'Arg2'),\n 'body': 'Hi, thanks for writing.',\n 'kwargs': {'abc': 123, 'xyz': 789},\n 'messageid': '270c8371-0467-4648-9630-5376e33ababa',\n 'recipient': 'hello',\n 'sender': 'main',\n 'subject': 'reply',\n 'threadid': '05cc7e85-f291-4c63-af34-6d47b8fc2594',\n 'threadindex': 2,\n 'time': 123456789.0015\n}\n```\n\n#### Explanation\n\nDue to the way that we set the frequencies above, the main process is checking for messages five times per second, while as the \"Hello, World!\" sub-process is sending one message every 5 seconds.\n\nBesides arbitrary headers that you can add (e.g. `args` and `kwargs` in the call to `send()` above), additional headers are added to this message (e.g. Sender, Time in [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), Message ID, and Thread ID) which can be used for other purposes, such as waiting for a reply. Just like e-mail, the Message ID is different, but the Thread ID is the same, and the Thread Index has incremented.\n\n## Contributors\n - [Louis T. Getterman IV](https://thad.getterman.org/about)\n\nHave something to contribute to this project? Please see the document, [How to contribute](HOW_TO_CONTRIBUTE.md).\n\n## Statistics\n\n[![Pipeline Status](https://gitlab.com/ltgiv/polycephaly/badges/master/pipeline.svg)](https://gitlab.com/ltgiv/polycephaly)\n\n[![Downloads](https://pepy.tech/badge/polycephaly/week)](https://pepy.tech/project/polycephaly/week)\n\n[![Downloads](https://pepy.tech/badge/polycephaly/month)](https://pepy.tech/project/polycephaly/month)\n\n[![Downloads](https://pepy.tech/badge/polycephaly)](https://pepy.tech/project/polycephaly)\n\n## Links\n\n - [Package](https://pypi.org/project/Polycephaly/)\n - [Documentation](https://ltgiv.gitlab.io/polycephaly/)\n - [Source](https://gitlab.com/ltgiv/polycephaly)\n - [Tracker](https://gitlab.com/ltgiv/polycephaly/issues)\n\n## License\n\n```\nMIT License\n\nCopyright (c) 2019 Louis T. Getterman IV\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n> Written with [StackEdit](https://stackedit.io/).\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://gitlab.com/ltgiv/polycephaly", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "Polycephaly", "package_url": "https://pypi.org/project/Polycephaly/", "platform": "", "project_url": "https://pypi.org/project/Polycephaly/", "project_urls": { "Documentation": "https://ltgiv.gitlab.io/polycephaly/", "Homepage": "https://gitlab.com/ltgiv/polycephaly", "Source": "https://gitlab.com/ltgiv/polycephaly", "Tracker": "https://gitlab.com/ltgiv/polycephaly/issues" }, "release_url": "https://pypi.org/project/Polycephaly/2019.11a5/", "requires_dist": [ "Logbook" ], "requires_python": "", "summary": "Easily create system daemons (and programs) that use an email-like syntax for communicating between threaded and forked processes.", "version": "2019.11a5", "yanked": false, "yanked_reason": null }, "last_serial": 6141356, "releases": { "2019.11a1": [ { "comment_text": "", "digests": { "md5": "01439a10929f68c9e43629f95180f9a0", "sha256": "9c3bddda3653f144ec9845d5206744cfbe2eccadfbfa05a168b94f42786f1ff9" }, "downloads": -1, "filename": "Polycephaly-2019.11a1-py3-none-any.whl", "has_sig": false, "md5_digest": "01439a10929f68c9e43629f95180f9a0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 65649, "upload_time": "2019-11-04T11:22:26", "upload_time_iso_8601": "2019-11-04T11:22:26.425796Z", "url": "https://files.pythonhosted.org/packages/9d/94/5500eb13a43bb93dfe27ca76dd85cbd64089b4034dcb19df1fe74371cab2/Polycephaly-2019.11a1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "b19e8e1a58cd58e24bca438881336fb8", "sha256": "6625c6f2a396009e0bca841cdc9f64b02332abbe79b0a4e473f8e659113016ce" }, "downloads": -1, "filename": "Polycephaly-2019.11a1.tar.gz", "has_sig": false, "md5_digest": "b19e8e1a58cd58e24bca438881336fb8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46611, "upload_time": "2019-11-04T11:22:28", "upload_time_iso_8601": "2019-11-04T11:22:28.487096Z", "url": "https://files.pythonhosted.org/packages/9d/88/1fe0471f04e1f11a89674759800888e3a20f823e9aa553115f8457cc0b7c/Polycephaly-2019.11a1.tar.gz", "yanked": false, "yanked_reason": null } ], "2019.11a2": [ { "comment_text": "", "digests": { "md5": "86948ac99a87dc9385a413a369a1a66c", "sha256": "ea9d06c432a98c86510a34e3e2a75ce5ea20ab1b4caedc721799859484067423" }, "downloads": -1, "filename": "Polycephaly-2019.11a2-py3-none-any.whl", "has_sig": false, "md5_digest": "86948ac99a87dc9385a413a369a1a66c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 72517, "upload_time": "2019-11-11T07:28:05", "upload_time_iso_8601": "2019-11-11T07:28:05.684798Z", "url": "https://files.pythonhosted.org/packages/0e/55/4af10dfdd4f9247c51eb52dde50e5d7706eeb88f04ea3d19b3e995ca9c09/Polycephaly-2019.11a2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "101181005c09ecfc801ca094c821db5c", "sha256": "7ecdd833e33b83b7bfc9f755397bdd48a4ce51bcc77418c646d9ae6476a878f5" }, "downloads": -1, "filename": "Polycephaly-2019.11a2.tar.gz", "has_sig": false, "md5_digest": "101181005c09ecfc801ca094c821db5c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52214, "upload_time": "2019-11-11T07:28:07", "upload_time_iso_8601": "2019-11-11T07:28:07.483583Z", "url": "https://files.pythonhosted.org/packages/0d/ea/7e6d127767e1d4279567b6a3996ba9c6c93c4c10dbf571432242cf118240/Polycephaly-2019.11a2.tar.gz", "yanked": false, "yanked_reason": null } ], "2019.11a3": [ { "comment_text": "", "digests": { "md5": "1692f6ddad93f3e07621d29530bb417a", "sha256": "56ae50c49f692034e230d31a216074ae08ea8e019d5e63a23521e66c8409d014" }, "downloads": -1, "filename": "Polycephaly-2019.11a3-py3-none-any.whl", "has_sig": false, "md5_digest": "1692f6ddad93f3e07621d29530bb417a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 72514, "upload_time": "2019-11-11T07:33:50", "upload_time_iso_8601": "2019-11-11T07:33:50.907187Z", "url": "https://files.pythonhosted.org/packages/0f/49/3914303c985a6e91b2c2093c86047ab4cac05c5c9a5e2c98a064e3744461/Polycephaly-2019.11a3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "5b105cd2ea73c4091613cc80309533e9", "sha256": "87fab79cfad655a63071ef0289fbfc5b3080b7bf165a42ab3caa969ffa1f2d7f" }, "downloads": -1, "filename": "Polycephaly-2019.11a3.tar.gz", "has_sig": false, "md5_digest": "5b105cd2ea73c4091613cc80309533e9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 52185, "upload_time": "2019-11-11T07:33:52", "upload_time_iso_8601": "2019-11-11T07:33:52.758244Z", "url": "https://files.pythonhosted.org/packages/ae/91/dec249f2a9f4a3ecd4bf066d4168178455fbd6652b2376f1e474fdaaba69/Polycephaly-2019.11a3.tar.gz", "yanked": false, "yanked_reason": null } ], "2019.11a4": [ { "comment_text": "", "digests": { "md5": "722a962cb08c7101a55256a065f720f2", "sha256": "8bf4cc4770f6859dbcac5c5abfd8967556a29842d461c316ce42f4daac95d23a" }, "downloads": -1, "filename": "Polycephaly-2019.11a4-py3-none-any.whl", "has_sig": false, "md5_digest": "722a962cb08c7101a55256a065f720f2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 72601, "upload_time": "2019-11-12T02:49:10", "upload_time_iso_8601": "2019-11-12T02:49:10.323761Z", "url": "https://files.pythonhosted.org/packages/83/84/95edf3ac6b397713ebf37d26c256f2597222d314b4dfaeda278d67a4c29d/Polycephaly-2019.11a4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "ddb04e18da49728470b4d4dfd18fa7ea", "sha256": "a22b2ddc73e7c0f5dc3c07bf3723ef3ef920cadb88d2e9522ec1677556bd6c86" }, "downloads": -1, "filename": "Polycephaly-2019.11a4.tar.gz", "has_sig": false, "md5_digest": "ddb04e18da49728470b4d4dfd18fa7ea", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 53188, "upload_time": "2019-11-12T02:49:12", "upload_time_iso_8601": "2019-11-12T02:49:12.116111Z", "url": "https://files.pythonhosted.org/packages/a5/68/248e9d3ea8eaeea021430044a3d41781ae0b54a873e5d2f71c6f5674e936/Polycephaly-2019.11a4.tar.gz", "yanked": false, "yanked_reason": null } ], "2019.11a5": [ { "comment_text": "", "digests": { "md5": "8624a663d425c0645813362d43746077", "sha256": "31105e9a7849bef6f99725a06317d744aa3ec0bf7acbbce66cfdd89f4eca26ab" }, "downloads": -1, "filename": "Polycephaly-2019.11a5-py3-none-any.whl", "has_sig": false, "md5_digest": "8624a663d425c0645813362d43746077", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 74672, "upload_time": "2019-11-15T05:16:36", "upload_time_iso_8601": "2019-11-15T05:16:36.032570Z", "url": "https://files.pythonhosted.org/packages/1a/a2/2b077dafc22e37869e791e8858ce940e6d7f9a3810240ac1fc280d617f2f/Polycephaly-2019.11a5-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "342d2632884057876c7bdc40686a5f59", "sha256": "9d516d41d33d4e62ee4d38ecb029af6905305e34ba2e7efc83508ac68c022f6e" }, "downloads": -1, "filename": "Polycephaly-2019.11a5.tar.gz", "has_sig": false, "md5_digest": "342d2632884057876c7bdc40686a5f59", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 54853, "upload_time": "2019-11-15T05:16:37", "upload_time_iso_8601": "2019-11-15T05:16:37.570469Z", "url": "https://files.pythonhosted.org/packages/2b/53/5b7e18574648b9013cc9a907f0c87c2a02e82b91f0a6b552870a3612d389/Polycephaly-2019.11a5.tar.gz", "yanked": false, "yanked_reason": null } ], "2019.8a1": [ { "comment_text": "", "digests": { "md5": "2d0f43f97141dd67f2e2eba7fe4a1e3d", "sha256": "5a1f464acfab54913367376141a77730bff19ddfba20de9a35a01b20e8b90633" }, "downloads": -1, "filename": "polycephaly-2019.8a1-py3-none-any.whl", "has_sig": false, "md5_digest": "2d0f43f97141dd67f2e2eba7fe4a1e3d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 47005, "upload_time": "2019-10-28T05:06:20", "upload_time_iso_8601": "2019-10-28T05:06:20.916280Z", "url": "https://files.pythonhosted.org/packages/b6/27/802ecfdc230e8d1c8b8b634f0e1f7fa17f4e3d884535c237aa5fdeab2973/polycephaly-2019.8a1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "02d53c99fe6ee413c1c019fdb6da1aa9", "sha256": "8d9f38d5a54f021d6089d37920776ffb1b484100e98c8a65f735a54df66163f2" }, "downloads": -1, "filename": "polycephaly-2019.8a1.tar.gz", "has_sig": false, "md5_digest": "02d53c99fe6ee413c1c019fdb6da1aa9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35212, "upload_time": "2019-10-28T05:06:23", "upload_time_iso_8601": "2019-10-28T05:06:23.286779Z", "url": "https://files.pythonhosted.org/packages/1d/47/92c6823772c77806032bf62bd241c1f67b4c83873486446919195faac9d9/polycephaly-2019.8a1.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "8624a663d425c0645813362d43746077", "sha256": "31105e9a7849bef6f99725a06317d744aa3ec0bf7acbbce66cfdd89f4eca26ab" }, "downloads": -1, "filename": "Polycephaly-2019.11a5-py3-none-any.whl", "has_sig": false, "md5_digest": "8624a663d425c0645813362d43746077", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 74672, "upload_time": "2019-11-15T05:16:36", "upload_time_iso_8601": "2019-11-15T05:16:36.032570Z", "url": "https://files.pythonhosted.org/packages/1a/a2/2b077dafc22e37869e791e8858ce940e6d7f9a3810240ac1fc280d617f2f/Polycephaly-2019.11a5-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "342d2632884057876c7bdc40686a5f59", "sha256": "9d516d41d33d4e62ee4d38ecb029af6905305e34ba2e7efc83508ac68c022f6e" }, "downloads": -1, "filename": "Polycephaly-2019.11a5.tar.gz", "has_sig": false, "md5_digest": "342d2632884057876c7bdc40686a5f59", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 54853, "upload_time": "2019-11-15T05:16:37", "upload_time_iso_8601": "2019-11-15T05:16:37.570469Z", "url": "https://files.pythonhosted.org/packages/2b/53/5b7e18574648b9013cc9a907f0c87c2a02e82b91f0a6b552870a3612d389/Polycephaly-2019.11a5.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }