{ "info": { "author": "Jonathan Robson", "author_email": "jnrbsn@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5" ], "description": ".. image:: https://travis-ci.org/jnrbsn/daemonocle.svg?branch=master\n :target: https://travis-ci.org/jnrbsn/daemonocle\n\n.. image:: https://coveralls.io/repos/github/jnrbsn/daemonocle/badge.svg?branch=master\n :target: https://coveralls.io/github/jnrbsn/daemonocle\n\ndaemonocle is a library for creating your own Unix-style daemons written in Python. It solves many\nproblems that other daemon libraries have and provides some really useful features you don't often\nsee in other daemons.\n\n.. contents:: **Table of Contents**\n :backlinks: none\n\nInstallation\n------------\n\nTo install via pip::\n\n pip install daemonocle\n\nOr download the source code and install manually::\n\n git clone https://github.com/jnrbsn/daemonocle.git\n cd daemonocle/\n python setup.py install\n\nBasic Usage\n-----------\n\nHere's a **really really** basic example:\n\n.. code:: python\n\n import sys\n import time\n\n import daemonocle\n\n # This is your daemon. It sleeps, and then sleeps again.\n def main():\n while True:\n time.sleep(10)\n\n if __name__ == '__main__':\n daemon = daemonocle.Daemon(\n worker=main,\n pidfile='/var/run/daemonocle_example.pid',\n )\n daemon.do_action(sys.argv[1])\n\nAnd here's the same example with logging and a `Shutdown Callback`_:\n\n.. code:: python\n\n import logging\n import sys\n import time\n\n import daemonocle\n\n def cb_shutdown(message, code):\n logging.info('Daemon is stopping')\n logging.debug(message)\n\n def main():\n logging.basicConfig(\n filename='/var/log/daemonocle_example.log',\n level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s',\n )\n logging.info('Daemon is starting')\n while True:\n logging.debug('Still running')\n time.sleep(10)\n\n if __name__ == '__main__':\n daemon = daemonocle.Daemon(\n worker=main,\n shutdown_callback=cb_shutdown,\n pidfile='/var/run/daemonocle_example.pid',\n )\n daemon.do_action(sys.argv[1])\n\nAnd here's what it looks like when you run it::\n\n user@host:~$ python example.py start\n Starting example.py ... OK\n user@host:~$ python example.py status\n example.py -- pid: 1234, status: running, uptime: 1m, %cpu: 0.0, %mem: 0.0\n user@host:~$ python example.py stop\n Stopping example.py ... OK\n user@host:~$ cat /var/log/daemonocle_example.log\n 2014-05-04 12:39:21,090 [INFO] Daemon is starting\n 2014-05-04 12:39:21,091 [DEBUG] Still running\n 2014-05-04 12:39:31,091 [DEBUG] Still running\n 2014-05-04 12:39:41,091 [DEBUG] Still running\n 2014-05-04 12:39:51,093 [DEBUG] Still running\n 2014-05-04 12:40:01,094 [DEBUG] Still running\n 2014-05-04 12:40:07,113 [INFO] Daemon is stopping\n 2014-05-04 12:40:07,114 [DEBUG] Terminated by SIGTERM (15)\n\nFor more details, see the `Detailed Usage`_ section below.\n\nRationale\n---------\n\nIf you think about it, a lot of Unix daemons don't really know what the hell they're doing. Have you\never found yourself in a situation that looked something like this? ::\n\n user@host:~$ sudo example start\n starting example ... ok\n user@host:~$ ps aux | grep example\n user 1234 0.0 0.0 1234 1234 pts/1 S+ 12:34 0:00 grep example\n user@host:~$ sudo example start\n starting example ... ok\n user@host:~$ echo $?\n 0\n user@host:~$ tail -f /var/log/example.log\n ...\n\nOr something like this? ::\n\n user@host:~$ sudo example stop\n stopping example ... ok\n user@host:~$ ps aux | grep example\n user 123 0.0 0.0 1234 1234 ? Ss 00:00 0:00 /usr/local/bin/example\n user 1234 0.0 0.0 1234 1234 pts/1 S+ 12:34 0:00 grep example\n user@host:~$ sudo example stop\n stopping example ... ok\n user@host:~$ ps aux | grep example\n user 123 0.0 0.0 1234 1234 ? Ss 00:00 0:00 /usr/local/bin/example\n user 1240 0.0 0.0 1234 1234 pts/1 S+ 12:34 0:00 grep example\n user@host:~$ sudo kill -9 123\n ...\n\nOr something like this? ::\n\n user@host:~$ sudo example status\n Usage: example {start|stop|restart}\n user@host:~$ ps aux | grep example\n ...\n\nThese are just a few examples of unnecessarily common problems. It doesn't have to be this way.\n\n **Note:** You might be thinking, \"Why not just write a smarter start/stop shell script wrapper\n for your daemon that checks whether or not it actually started, actually stopped, etc.?\"\n Seriously? **It doesn't have to be this way.** I believe daemons should be more self-aware. They\n should handle their own problems most of the time, and your start/stop script should only be a\n very thin wrapper around your daemon or simply a symlink to your daemon.\n\nThe Problem\n~~~~~~~~~~~\n\nIf you've ever dug deep into the nitty-gritty details of how daemonization works, you're probably\nfamiliar with the `standard \"double fork\" paradigm `_ first introduced\nby W. Richard Stevens in the book `Advanced Programming in the UNIX Environment\n`_. One of the problems with the standard way to implement this is that\nif the final child dies immediately when it gets around to doing real work, the original parent\nprocess (the one that actually had control of your terminal) is long gone. So all you know is that\nthe process got forked, but you have no idea if it actually kept running for more than a fraction of\na second. And let's face it, one of the most likely times for a daemon to die is immediately after\nit starts (due to bad configuration, permissions, etc.).\n\nThe next problem mentioned in the section above is when you try to stop a daemon, it doesn't\nactually stop, and you have no idea that it didn't actually stop. This happens when a process\ndoesn't respond properly to a ``SIGTERM`` signal. It happens more often than it should. The problem\nis not necessarily the fact that it didn't stop. It's the fact that you didn't *know* that it didn't\nstop. The start/stop script knows that it successfully sent the signal and so it assumes success.\nThis also becomes a problem when your ``restart`` command blindly calls ``stop`` and then ``start``,\nbecause it will try to start a new instance of the daemon before the previous one has exited.\n\nThese are the biggest problems most daemons have in my opinion. daemonocle solves these problems and\nprovides many other \"fancy\" features.\n\nThe Solution\n~~~~~~~~~~~~\n\nThe problem with the daemon immediately dying on startup and you not knowing about it is solved by\nhaving the first child (the immediate parent of the final child) sleep for one second and then call\n``os.waitpid(pid, os.WNOHANG)`` to see if the process is still running. This is what daemonocle\ndoes. So if you're daemon dies within one second of starting, you'll know about it.\n\nThis problem with the daemon not stopping and you not knowing about it is solved by simply waiting\nfor the process to finish (with a timeout). This is what daemonocle does. (Note: When a timeout\noccurs, it doesn't try to send a ``SIGKILL``. This is not always what you'd want and often not a\ngood idea.)\n\nOther Useful Features\n~~~~~~~~~~~~~~~~~~~~~\n\nBelow are some other useful features that daemononcle provides that you might not find elsewhere.\n\nThe ``status`` Action\n+++++++++++++++++++++\n\nThere is a ``status`` action that not only displays whether or not the daemon is running and its\nPID, but also the uptime of the daemon and the % CPU and % memory usage of all the processes in the\nsame process group as the daemon (which are probably its children). So if you have a daemon that\nlaunches mulitple worker processes, the ``status`` action will show the % CPU and % memory usage of\nall the workers combined.\n\nIt might look something like this::\n\n user@host:~$ python example.py status\n example.py -- pid: 1234, status: running, uptime: 12d 3h 4m, %cpu: 12.4, %mem: 4.5\n\nSlightly Smarter ``restart`` Action\n+++++++++++++++++++++++++++++++++++\n\nHave you ever tried to restart a daemon only to realize that it's not actually running? Let me\nguess: it just gave you an error and didn't start the daemon. A lot of the time this is not a\nproblem, but if you're trying to restart the daemon in an automated way, it's more annoying to have\nto check if it's running and do either a ``start`` or ``restart`` accordingly. With daemonocle, if\nyou try to restart a daemon that's not running, it will give you a warning saying that it wasn't\nrunning and then start the daemon. This is often what people expect.\n\nSelf-Reload\n+++++++++++\n\nDaemons that use daemonocle have the ability to reload themselves by simply calling\n``daemon.reload()`` where ``daemon`` is your ``daemonocle.Daemon`` instance. The execution of the\ncurrent daemon halts wherever ``daemon.reload()`` was called, and a new daemon is started up to\nreplace the current one. From your code's perspective, it's pretty much the same as a doing a\n``restart`` except that it's initiated from within the daemon itself and there's no signal handling\ninvolved. Here's a basic example of a daemon that watches a config file and reloads itself when the\nconfig file changes:\n\n.. code:: python\n\n import os\n import sys\n import time\n\n import daemonocle\n\n class FileWatcher(object):\n\n def __init__(self, filename, daemon):\n self._filename = filename\n self._daemon = daemon\n self._file_mtime = os.stat(self._filename).st_mtime\n\n def file_has_changed(self):\n current_mtime = os.stat(self._filename).st_mtime\n if current_mtime != self._file_mtime:\n self._file_mtime = current_mtime\n return True\n return False\n\n def watch(self):\n while True:\n if self.file_has_changed():\n self._daemon.reload()\n time.sleep(1)\n\n if __name__ == '__main__':\n daemon = daemonocle.Daemon(pidfile='/var/run/daemonocle_example.pid')\n fw = FileWatcher(filename='/etc/daemonocle_example.conf', daemon=daemon)\n daemon.worker = fw.watch\n daemon.do_action(sys.argv[1])\n\nShutdown Callback\n+++++++++++++++++\n\nYou may have noticed from the `Basic Usage`_ section above that a ``shutdown_callback`` was defined.\nThis function gets called whenever the daemon is shutting down in a catchable way, which should be\nmost of the time except for a ``SIGKILL`` or if your server crashes unexpectedly or loses power or\nsomething like that. This function can be used for doing any sort of cleanup that your daemon needs\nto do. Also, if you want to log (to the logger of your choice) the reason for the shutdown and the\nintended exit code, you can use the ``message`` and ``code`` arguments that will be passed to your\ncallback (your callback must take these two arguments).\n\nNon-Detached Mode\n+++++++++++++++++\n\nThis is not particularly interesting per se, but it's worth noting that in non-detached mode, your\ndaemon will do everything else you've configured it to do (i.e. ``setuid``, ``setgid``, ``chroot``,\netc.) except actually detaching from your terminal. So while you're testing, you can get an\nextremely accurate view of how your daemon will behave in the wild. It's also worth noting that\nself-reloading works in non-detached mode, which was a little tricky to figure out initially.\n\nFile Descriptor Handling\n++++++++++++++++++++++++\n\nOne of the things that daemons typically do is close all open file descriptors and establish new\nones for ``STDIN``, ``STDOUT``, ``STDERR`` that just point to ``/dev/null``. This is fine most of\nthe time, but if your worker is an instance method of a class that opens files in its ``__init__()``\nmethod, then you'll run into problems if you're not careful. This is also a problem if you're\nimporting a module that leaves open files behind. For example, importing the\n`random `_ standard library module in Python 3\nresults in an open file descriptor for ``/dev/urandom``.\n\nSince this \"feature\" of daemons often causes more problems than it solves, and the problems it\ncauses sometimes have strange side-effects that make it very difficult to troubleshoot, this feature\nis optional and disabled by default in daemonocle via the ``close_open_files`` option.\n\nDetailed Usage\n--------------\n\nThe ``daemonocle.Daemon`` class is the main class for creating a daemon using daemonocle. Here's the\nconstructor signature for the class:\n\n.. code:: python\n\n class daemonocle.Daemon(\n worker=None, shutdown_callback=None, prog=None, pidfile=None, detach=True,\n uid=None, gid=None, workdir='/', chrootdir=None, umask=022, stop_timeout=10,\n close_open_files=False)\n\nAnd here are descriptions of all the arguments:\n\n**worker**\n The function that does all the work for your daemon.\n\n**shutdown_callback**\n This will get called anytime the daemon is shutting down. It should take a ``message`` and a\n ``code`` argument. The message is a human readable message that explains why the daemon is\n shutting down. It might useful to log this message. The code is the exit code with which it\n intends to exit. See `Shutdown Callback`_ for more details.\n\n**prog**\n The name of your program to use in output messages. Default: ``os.path.basename(sys.argv[0])``\n\n**pidfile**\n The path to a PID file to use. It's not required to use a PID file, but if you don't, you won't\n be able to use all the features you might expect. Make sure the user your daemon is running as\n has permission to write to the directory this file is in.\n\n**detach**\n Whether or not to detach from the terminal and go into the background. See `Non-Detached Mode`_\n for more details. Default: ``True``\n\n**uid**\n The user ID to switch to when the daemon starts. The default is not to switch users.\n\n**gid**\n The group ID to switch to when the daemon starts. The default is not to switch groups.\n\n**workdir**\n The path to a directory to change to when the daemon starts. Note that a file system cannot be\n unmounted if a process has its working directory on that file system. So if you change the\n default, be careful about what you change it to. Default: ``\"/\"``\n\n**chrootdir**\n The path to a directory to set as the effective root directory when the daemon starts. The\n default is not to do anything.\n\n**umask**\n The file creation mask (\"umask\") for the process. Default: ``022``\n\n**stop_timeout**\n Number of seconds to wait for the daemon to stop before throwing an error. Default: ``10``\n\n**close_open_files**\n Whether or not to close all open files when the daemon detaches. Default: ``False``\n\nActions\n~~~~~~~\n\nThe default actions are ``start``, ``stop``, ``restart``, and ``status``. You can get a list of\navailable actions using the ``daemonocle.Daemon.list_actions()`` method. The recommended way to call\nan action is using the ``daemonocle.Daemon.do_action(action)`` method. The string name of an action\nis the same as the method name except with dashes in place of underscores.\n\nIf you want to create your own actions, simply subclass ``daemonocle.Daemon`` and add the\n``@daemonocle.expose_action`` decorator to your action method, and that's it.\n\nHere's an example:\n\n.. code:: python\n\n import daemonocle\n\n class MyDaemon(daemonocle.Daemon):\n\n @daemonocle.expose_action\n def full_status(self):\n \"\"\"Get more detailed status of the daemon.\"\"\"\n pass\n\nThen, if you did the basic ``daemon.do_action(sys.argv[1])`` like in all the examples above, you can\ncall your action with a command like ``python example.py full-status``.\n\nIntegration with mitsuhiko's click\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\ndaemonocle also provides an integration with `click `_, the \"composable\ncommand line utility\". The integration is in the form of a custom command class\n``daemonocle.cli.DaemonCLI`` that you can use in conjunction with the ``@click.command()`` decorator\nto automatically generate a command line interface with subcommands for all your actions. It also\nautomatically daemonizes the decorated function. The decorated function becomes the worker, and the\nactions are automatically mapped from click to daemonocle.\n\nHere's an example:\n\n.. code:: python\n\n import time\n\n import click\n from daemonocle.cli import DaemonCLI\n\n @click.command(cls=DaemonCLI, daemon_params={'pidfile': '/var/run/example.pid'})\n def main():\n \"\"\"This is my awesome daemon. It pretends to do work in the background.\"\"\"\n while True:\n time.sleep(10)\n\n if __name__ == '__main__':\n main()\n\nRunning this example would look something like this::\n\n user@host:~$ python example.py --help\n Usage: example.py [] []...\n\n This is my awesome daemon. It pretends to do work in the background.\n\n Options:\n --help Show this message and exit.\n\n Commands:\n start Start the daemon.\n stop Stop the daemon.\n restart Stop then start the daemon.\n status Get the status of the daemon.\n user@host:~$ python example.py start --help\n Usage: example.py start []\n\n Start the daemon.\n\n Options:\n --debug Do NOT detach and run in the background.\n --help Show this message and exit.\n\nThe ``daemonocle.cli.DaemonCLI`` class also accepts a ``daemon_class`` argument that can be a\nsubclass of ``daemonocle.Daemon``. It will use your custom class, automatically create subcommands\nfor any custom actions you've defined, and use the docstrings of the action methods as the help text\njust like click usually does.\n\nThis integration is entirely optional. daemonocle doesn't enforce any sort of argument parsing. You\ncan use argparse, optparse, or just plain ``sys.argv`` if you want.\n\nBugs, Requests, Questions, etc.\n-------------------------------\n\nPlease create an `issue on GitHub `_.\n\nRelease History\n---------------\n\nv1.0.1 (2016-04-17)\n~~~~~~~~~~~~~~~~~~~\n\n* No changes in this release. Bumped version only to re-upload to PyPI.\n\nv1.0.0 (2016-04-17)\n~~~~~~~~~~~~~~~~~~~\n\n* Added official support for Python 2.7, 3.3, 3.4, and 3.5.\n* Added a comprehensive suite of unit tests with over 90% code coverage.\n* Dependencies (click and psutil) are no longer pinned to specific versions.\n* Fixed bug with ``atexit`` handlers not being called in intermediate processes.\n* Fixed bug when PID file is a relative path.\n* Fixed bug when STDIN doesn't have a file descriptor number.\n* Fixed bug when running in non-detached mode in a Docker container.\n* A TTY is no longer checked for when deciding how to run in non-detached mode.\n The behavior was inconsistent across different platforms.\n* Fixed bug when a process stopped before having chance to check if it stopped.\n* Fixed bug where an exception could be raised if a PID file is already gone\n when trying to remove it.\n* Subdirectories created for PID files now respect the ``umask`` setting.\n* The pre-``umask`` mode for PID files is now ``0o666`` instead of ``0o777``,\n which will result in a default mode of ``0o644`` instead of ``0o755`` when\n using the default ``umask`` of ``0o22``.\n\nv0.8 (2014-08-01)\n~~~~~~~~~~~~~~~~~\n\n* Upgraded click to version 2.5.\n* Status action now returns exit code 1 if the daemon is not running.\n\nv0.7 (2014-06-23)\n~~~~~~~~~~~~~~~~~\n\n* Fixed bug that was causing an empty PID file on Python 3.\n* Upgraded click to version 2.1.\n* Open file discriptors are no longer closed by default. This functionality is now optional via the\n ``close_open_files`` argument to ``Daemon()``.\n* Added ``is_worker`` argument to ``DaemonCLI()`` as well as the ``pass_daemon`` decorator.\n\nv0.6 (2014-06-10)\n~~~~~~~~~~~~~~~~~\n\n* Upgraded click to version 2.0.\n\nv0.5 (2014-06-09)\n~~~~~~~~~~~~~~~~~\n\n* Fixed literal octal formatting to work with Python 3.\n\nv0.4 (2014-05-19)\n~~~~~~~~~~~~~~~~~\n\n* Fixed bug with uptime calculation in status action.\n* Upgraded click to version 0.7.\n\nv0.3 (2014-05-14)\n~~~~~~~~~~~~~~~~~\n\n* Reorganized package and cleaned up code.\n\nv0.2 (2014-05-12)\n~~~~~~~~~~~~~~~~~\n\n* Renamed ``Daemon.get_actions()`` to ``Daemon.list_actions()``.\n* Improvements to documentation.\n* Fixed bug with non-detached mode when parent is in the same process group.\n\nv0.1 (2014-05-11)\n~~~~~~~~~~~~~~~~~\n\n* Initial release.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/jnrbsn/daemonocle", "keywords": "daemon daemonize fork unix cli", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "daemonocle", "package_url": "https://pypi.org/project/daemonocle/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/daemonocle/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/jnrbsn/daemonocle" }, "release_url": "https://pypi.org/project/daemonocle/1.0.1/", "requires_dist": null, "requires_python": null, "summary": "A Python library for creating super fancy Unix daemons", "version": "1.0.1" }, "last_serial": 2068881, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "73153c087c448f1c55b99e212a3b4616", "sha256": "5a278f4e3e8c1039f0fa29a2912bec8e3020f2f78840679c3c4d8a9f4ba13e43" }, "downloads": -1, "filename": "daemonocle-0.1.tar.gz", "has_sig": false, "md5_digest": "73153c087c448f1c55b99e212a3b4616", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15424, "upload_time": "2014-05-12T03:43:32", "url": "https://files.pythonhosted.org/packages/53/31/02d9f5879ce577ca8f10b53dad8b12842359c1dbc8a4a87664b406b7d419/daemonocle-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "20b0162543e39946ef5fa553afa95fc7", "sha256": "d0ad9398c2387da9b83012517b339aa5da20bdde79675f9a3ecf22bc89a5fdc7" }, "downloads": -1, "filename": "daemonocle-0.2.tar.gz", "has_sig": false, "md5_digest": "20b0162543e39946ef5fa553afa95fc7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15500, "upload_time": "2014-05-13T00:32:54", "url": "https://files.pythonhosted.org/packages/52/5b/90830d55943860de58f756793123a4759cf912987a3bf16970fd14b1e15a/daemonocle-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "38422acfb19100b249ce7b47b491bfab", "sha256": "2da7d993e19011f1b30c12fa09df6e8184e379e54f124015aff5bb22060491bc" }, "downloads": -1, "filename": "daemonocle-0.3.tar.gz", "has_sig": false, "md5_digest": "38422acfb19100b249ce7b47b491bfab", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15928, "upload_time": "2014-05-15T07:49:49", "url": "https://files.pythonhosted.org/packages/16/c1/03185cb143b4e750a4866b5cad01713cb5f1f6cb3b74402ac6927b6adfc7/daemonocle-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "79edf0d7c98003432b7f9c4274710025", "sha256": "29e2f102806e361dfd7257d0de426d3187f0c578b6b93a2279743aa5cae9a2bf" }, "downloads": -1, "filename": "daemonocle-0.4.tar.gz", "has_sig": false, "md5_digest": "79edf0d7c98003432b7f9c4274710025", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15936, "upload_time": "2014-05-19T21:27:56", "url": "https://files.pythonhosted.org/packages/4f/5c/d46ee511c8e2bb1b5033cd5c5ab374990cad05b66259f64c6f97828a8c9c/daemonocle-0.4.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "97cfd7541dbab90b5348f94f2efd9ba1", "sha256": "f00e84b4487eb7c9039943c386f1f4e3338492eed7ad31a8d1186e450715c047" }, "downloads": -1, "filename": "daemonocle-0.5.tar.gz", "has_sig": false, "md5_digest": "97cfd7541dbab90b5348f94f2efd9ba1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15937, "upload_time": "2014-06-10T04:27:30", "url": "https://files.pythonhosted.org/packages/fb/5a/eb5e0a6a4af24946471a4039cb005a8c12202bf4e6fbfd3da4507f946090/daemonocle-0.5.tar.gz" } ], "0.6": [ { "comment_text": "", "digests": { "md5": "24b384275afaaa16623c741d1d54a3e3", "sha256": "f1d810497fbe5b1a22765fa22b583f841429e37bbb66f7d63956772956d30215" }, "downloads": -1, "filename": "daemonocle-0.6.tar.gz", "has_sig": false, "md5_digest": "24b384275afaaa16623c741d1d54a3e3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15938, "upload_time": "2014-06-11T05:00:35", "url": "https://files.pythonhosted.org/packages/aa/0e/62a73e486f5cc388690b43997b8d68338d8bc9b3a3df7156bc68fe835a73/daemonocle-0.6.tar.gz" } ], "0.7": [ { "comment_text": "", "digests": { "md5": "b1eb29b15da3804bc6d091314bffffbe", "sha256": "191f451400c3a4c6e2760082c7a7835ecbe201db319202e6c42916c0b596338a" }, "downloads": -1, "filename": "daemonocle-0.7.tar.gz", "has_sig": false, "md5_digest": "b1eb29b15da3804bc6d091314bffffbe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16004, "upload_time": "2014-06-24T03:38:03", "url": "https://files.pythonhosted.org/packages/0c/c3/e73098713cbfacd429586f009e1d75570ce3585ea6061da284781d9e21af/daemonocle-0.7.tar.gz" } ], "0.8": [ { "comment_text": "", "digests": { "md5": "b0912dec23b42adc5aa55f0cb410dde0", "sha256": "b5fd390a3cf617c9113be02097892d2a0e967d99cc4ec9926671e20e8c03520c" }, "downloads": -1, "filename": "daemonocle-0.8.tar.gz", "has_sig": false, "md5_digest": "b0912dec23b42adc5aa55f0cb410dde0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16023, "upload_time": "2014-08-01T18:10:23", "url": "https://files.pythonhosted.org/packages/23/b5/7afd4328fd12b435cd908c922241f92183c0272574fa475f90b29006d25a/daemonocle-0.8.tar.gz" } ], "1.0.0": [], "1.0.1": [ { "comment_text": "", "digests": { "md5": "961dbab6b8d53095695c3ae3287feea3", "sha256": "a8fc48d55f6390302a9a1816ad488cba640e70948f750d4c8fe5a401294dab68" }, "downloads": -1, "filename": "daemonocle-1.0.1.tar.gz", "has_sig": false, "md5_digest": "961dbab6b8d53095695c3ae3287feea3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23399, "upload_time": "2016-04-18T02:36:31", "url": "https://files.pythonhosted.org/packages/9a/0d/e7b411174ccff6b342a80283b3dc9728807372b6a27febc279dd0f9057f2/daemonocle-1.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "961dbab6b8d53095695c3ae3287feea3", "sha256": "a8fc48d55f6390302a9a1816ad488cba640e70948f750d4c8fe5a401294dab68" }, "downloads": -1, "filename": "daemonocle-1.0.1.tar.gz", "has_sig": false, "md5_digest": "961dbab6b8d53095695c3ae3287feea3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23399, "upload_time": "2016-04-18T02:36:31", "url": "https://files.pythonhosted.org/packages/9a/0d/e7b411174ccff6b342a80283b3dc9728807372b6a27febc279dd0f9057f2/daemonocle-1.0.1.tar.gz" } ] }