{ "info": { "author": "Alexander Oblovatniy", "author_email": "oblovatniy@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "isotopic-logging\n================\n\n|pypi_package| |pypi_downloads| |python_versions| |license|\n\n|unix_build| |windows_build| |coverage_status|\n\n|code_issues| |codeclimate| |codacy| |quality| |health| |requirements|\n\n\n**Table of contents**\n\n.. contents::\n :local:\n :depth: 2\n :backlinks: none\n\n\nSynopsis\n--------\n\n``isotopic-logging`` is a little Python library which is designed to help you\nto track separate operations and their parts within whole execution flow. This\nis done by injecting operation prefixes at the beginning of log messages.\n\nThis library was born in depths of real projects which have web applications\nand background task queues, each of which can have multiple workers. There are\ntwo key points this library resolves:\n\n- As administrator I want to have log entries marked with same prefix\n within single operation so that I can distinguish and track operations even\n if log is written from multiple threads or sources.\n- As developer I want to store prefix in some context so that I do not need\n to format it per each call to logger and so that I can access it within\n nested function calls without passing prefix to a function directly and\n screwing up its semantics.\n\n``isotopic-logging`` comes very useful when you have a single log stream, which\nis populated from parallel sources (threads or processes), and you need to\ndetect flow of a single operation in a mess of interweaving log messages and to\ndistinguish different instances of the same operation.\n\nThe library can be useful for single-process and single-thread applications as\nwell. You may still need to detect operations and track their execution time\nand you can do it well.\n\n\nQuick output example\n--------------------\n\nFor example, log of a single complex operation may look like this:\n\n.. code-block::\n\n INFO [2015-12-15 21:45:04,339] D6EF95 | Heavy task has started.\n DEBUG [2015-12-15 21:46:36,148] D6EF95 | Checking user permissions.\n INFO [2015-12-15 21:46:36,654] D6EF95 | Analysis | Analysis phase has started.\n DEBUG [2015-12-15 21:46:41,756] D6EF95 | Analysis | Analysing current state of devices.\n DEBUG [2015-12-15 21:46:42,959] D6EF95 | Analysis | Analysing new state of devices.\n DEBUG [2015-12-15 21:46:47,565] D6EF95 | Analysis | Analysing changes.\n INFO [2015-12-15 21:46:51,871] D6EF95 | Analysis | Analysis phase has finished.\n INFO [2015-12-15 21:46:54,073] D6EF95 | Pushing data to central storage.\n INFO [2015-12-15 21:46:55,278] D6EF95 | Communication | Communication phase has started.\n DEBUG [2015-12-15 21:46:58,884] D6EF95 | Communication | Spreading out parallel subtasks for every involved device.\n DEBUG [2015-12-15 21:47:02,089] D6EF95 | Communication | 478272 | Connecting to device #3.\n DEBUG [2015-12-15 21:47:03,493] D6EF95 | Communication | 28B208 | Connecting to device #1.\n INFO [2015-12-15 21:47:04,798] D6EF95 | Communication | 28B208 | Running job at device #1.\n DEBUG [2015-12-15 21:47:10,501] D6EF95 | Communication | AE2677 | Connecting to device #2.\n INFO [2015-12-15 21:47:12,501] D6EF95 | Communication | AE2677 | Running job at device #2.\n INFO [2015-12-15 21:47:17,707] D6EF95 | Communication | 478272 | Running job at device #3.\n INFO [2015-12-15 21:47:21,709] D6EF95 | Communication | Communication phase has finished.\n DEBUG [2015-12-15 21:47:24,412] D6EF95 | Commiting changes.\n INFO [2015-12-15 21:47:27,013] D6EF95 | Heavy task has finished, elapsed time: 00:23:11.004120.\n\n\nImportant thing to note: each line of the example log from above may be\nproduced by different functions running in different threads or processes. And\nthey do not need to remember and pass logging prefixes from one to another\nwhich keeps you focused on development process and prevents you from\ndistracting by log message formatting.\n\n\nInstallation\n------------\n\nTo install the library simply get it at `Cheese Shop`_ (PyPI):\n\n.. code-block:: bash\n\n pip install isotopic-logging\n\n\nKey concepts\n------------\n\nWork of this library is based on several key concepts:\n\n- Prefix injectors: they store or/and generate prefixes and inject them into\n strings.\n- Injection contexts: they manage injectors (get or create them) and track\n scope execution time.\n- Injection scopes: they drive creation of injectors and bound operation\n execution time.\n- Logger wrapper: culmination of other concepts. Wraps loggers and provides\n methods for creation of injection contexts.\n\nThese concepts may be used separately or as a whole combination in form of\nlogger wrapper. Such approach is useful for flexible customization.\n\n\nPrefix injectors\n----------------\n\nPrefix injectors are objects which store or/and generate prefixes accessed by\n``prefix`` attribute and which are injected into target strings using\n``mark()`` method.\n\nDefault injectors are defined in ``isotopic_logging.injectors`` module and they\nare described below.\n\n\nDirect prefix injector\n~~~~~~~~~~~~~~~~~~~~~~\n\n``DirectPrefixInjector`` will inject into strings exactly given prefix:\n\n.. code-block:: python\n\n from isotopic_logging.injectors import DirectPrefixInjector\n\n inj = DirectPrefixInjector(\"foo > \")\n inj.mark(\"message\")\n # \"foo > message\"\n\nAll other injectors are subclasses of ``DirectPrefixInjector`` and usually you\nwill not need to use it directly. Exception is only the case when you need to\ntransmit prefix between processes or threads.\n\n\nStatic prefix injector\n~~~~~~~~~~~~~~~~~~~~~~\n\n``StaticPrefixInjector`` automatically inserts delimiter between prefix and\ntarget strings:\n\n.. code-block:: python\n\n from isotopic_logging.injectors import StaticPrefixInjector\n\n inj = StaticPrefixInjector(\"foo\")\n inj.mark(\"message\")\n # \"foo | message\"\n\nDefault delimiter is defined as ``isotopic_logging.defaults.DELIMITER`` as its\nvalue is ``\" | \"`` (space-pipe-space).\n\nYou can set custom delimiter:\n\n.. code-block:: python\n\n inj = StaticPrefixInjector(\"foo\", delimiter=\":\")\n inj.mark(\"message\")\n # \"foo:message\"\n\n\nAutoprefix injector\n~~~~~~~~~~~~~~~~~~~\n\n``AutoprefixInjector`` works like ``StaticPrefixInjector``, but it generates\nprefixes by itself.\n\nGenerally it is used to distinguish different instances of same operations or\ndifferent calls to same methods and so on.\n\n.. code-block:: python\n\n from isotopic_logging.injectors import AutoprefixInjector\n\n inj1 = AutoprefixInjector()\n inj1.mark(\"message\")\n # \"C220A0 | message\"\n\n inj2 = AutoprefixInjector()\n inj2.mark(\"message\")\n # \"4118BB | message\"\n\nHere you can see that 2 different injectors have 2 different prefixes.\n\nDefault prefixes are generated by threadsafe generator\n``isotopic_logging.generators.default_oid_generator`` which uses ``uuid.uuid4``\nto produce results.\n\nGiven default prefix lenght of 6 symbols, default generator guarantees that 99%\nof generated prefixes will be unique in case of 500 serial calls from 100\nparallel threads. It is considered to be enough to distinguish operations which\nare placed in time close to each other.\n\nYou can use custom generator:\n\n.. code-block:: python\n\n from itertools import cycle\n from isotopic_logging.injectors import AutoprefixInjector\n\n generator = cycle([\"foo\", \"bar\", ])\n\n inj1 = AutoprefixInjector(generator)\n inj1.mark(\"message\")\n # \"foo | message\"\n\n inj2 = AutoprefixInjector(generator)\n inj2.mark(\"message\")\n # \"bar | message\"\n\n\nIf you are sure you need custom generator, you must ensure that it's threadsafe.\nYou can use ``isotopic_logging.concurrency.threadsafe_iter`` for this:\n\n.. code-block:: python\n\n from isotopic_logging.concurrency import threadsafe_iter\n\n def generate():\n i = 1\n while True:\n yield \"gen-%d\" % i\n i += 1\n\n generator = threadsafe_iter(generate())\n\n``threadsafe_iter`` is needed for generators which are implemented in pure\nPython. For examle, in CPython ``itertools.cycle`` has native implementation\nand it's threadsafe out of the box. Moreover, looks like Python 3 makes your\ngenerators threadsafe as well, so it's quite possible that you will need\n``threadsafe_iter`` only for Python 2.\n\n``AutoprefixInjector`` also supports custom delimiters:\n\n.. code-block:: python\n\n inj = AutoprefixInjector(delimiter=\":\")\n inj.mark(\"message\")\n # \"74D3B2:message\"\n\n\nHybrid prefix injector\n~~~~~~~~~~~~~~~~~~~~~~\n\n``HybridPrefixInjector`` combines both features of ``AutoprefixInjector`` and\n``StaticPrefixInjector``: it creates prefixes which consist of generated part\nfollowed by static part which are separated by default or custom delimiter.\n\n.. code-block:: python\n\n from isotopic_logging.injectors import HybridPrefixInjector\n\n inj1 = HybridPrefixInjector(\"static\")\n inj1.mark(\"message\")\n # \"78E519 | static | message\"\n\n inj2 = HybridPrefixInjector(\"static\")\n inj2.mark(\"message\")\n # \"EF8A74 | static | message\"\n\nThis prefix injector also supports custom delimiter and generator:\n\n.. code-block:: python\n\n from itertools import cycle\n from isotopic_logging.injectors import HybridPrefixInjector\n\n generator = cycle([\"foo\", \"bar\", ])\n\n inj1 = HybridPrefixInjector(\"static\", generator, delimiter=\":\")\n inj1.mark(\"message\")\n # \"foo:static:message\"\n\n inj2 = HybridPrefixInjector(\"static\", generator, delimiter=\":\")\n inj2.mark(\"message\")\n # \"bar:static:message\"\n\n\nInjection contexts\n------------------\n\nInjection contexts are used for scope management. Scopes are described in\nthe next section.\n\nContexts are responsible for providing you with proper injectors. Injectors are\ncreated on demand. Generally, this can be described as:\n\n- \"Give me *current injector* or create new specific one if there is no *current injector*\"\n- or \"Create new injector inherited from *current one* despite anything\".\n\nContexts orginize injectors into stacks. Stacks are thread-local and do not\ninterfere with each other. There is no limit for stack size. This should not be\na problem, because injectors are created lazily. This happens only if stack is\nempty or if you explicitly want to inherit current prefix (usually to\ndistinguish suboperation).\n\n*Current injector* is the injector on top of the stack in current thread.\n\nInjection context managers are defined in ``isotopic_logging.context`` module.\nThere is a proper context manager for each type of prefix injector. Context\nmanagers accept accept same arguments as injectors which they are going to\nproduce.\n\nExamples:\n\n.. code-block:: python\n\n from isotopic_logging.context import direct_injector, static_injector\n from isotopic_logging.context import auto_injector, hybrid_injector\n\n with direct_injector(\"foo > \") as inj:\n inj.mark(\"message\")\n # \"foo > message\"\n\n with static_injector(\"foo\") as inj:\n inj.mark(\"message\")\n # \"foo | message\"\n\n with auto_injector() as inj:\n inj.mark(\"message\")\n # \"25EBB8 | message\"\n\n with hybrid_injector(\"static\") as inj:\n inj.mark(\"message\")\n # \"0F9A8F | static | message\"\n\n\nInjection scopes\n----------------\n\nScopes are created by contexts and they are used to drive creation of\ninjectors. There are two kinds of scopes: top-level and nested. Nested scopes\nallow inheritance of prefixes.\n\nLet's look at examples to grab the idea.\n\n\nNested scopes\n~~~~~~~~~~~~~\n\n.. code-block:: python\n\n from isotopic_logging.context import auto_injector, hybrid_injector\n\n def helper():\n with auto_injector() as inj:\n print(inj.mark(\"call from helper\"))\n\n def operation():\n with hybrid_injector(\"operation\") as inj:\n print(inj.mark(\"start\"))\n helper()\n print(inj.mark(\"end\"))\n\nHere we separate ``helper`` and ``operation`` functions. Both of them define\nown scopes via context managers.\n\nIf ``helper`` is called directly, it's scope will be *top-level* and new\ninjector will be created for each call:\n\n.. code-block:: python\n\n helper()\n # ED5ED5 | call from helper\n helper()\n # 14F7CE | call from helper\n\nIf ``helper`` will be called from ``operation``, it's scope will become\n*nested* and it will reuse injector created within top-level scope:\n\n.. code-block:: python\n\n operation()\n # A15324 | operation | start\n # A15324 | operation | call from helper\n # A15324 | operation | end\n\nIn this case ``inj`` in ``operation`` and ``inj`` in ``helper`` will be exactly\nthe same object.\n\n\nInherited scopes\n~~~~~~~~~~~~~~~~\n\nNested scopes are good if they are used within reusable helpers, utils, etc.,\nespecially if they are small. If nested calls present some complex operations,\nyou may want to separate them with own prefixes, but preserve parent prefix.\n\nYou can inherit current prefix to do so:\n\n.. code-block:: python\n\n from isotopic_logging.context import (\n auto_injector, static_injector, hybrid_injector,\n )\n\n def helper():\n with auto_injector() as inj:\n print(inj.mark(\"call from helper\"))\n\n def suboperation():\n with static_injector(\"suboperation\", inherit=True) as inj:\n print(inj.mark(\"start\"))\n helper()\n print(inj.mark(\"end\"))\n\n def operation():\n with hybrid_injector(\"operation\") as inj:\n print(inj.mark(\"start\"))\n suboperation()\n print(inj.mark(\"end\"))\n\n operation()\n # 9F3A34 | operation | start\n # 9F3A34 | operation | suboperation | start\n # 9F3A34 | operation | suboperation | call from helper\n # 9F3A34 | operation | suboperation | end\n # 9F3A34 | operation | end\n\nHere, ``suboperation`` uses ``static_injector`` with flag ``inherit=True``.\nThis creates new injector, which is a combination of parent prefix and given\nstatic prefix. ``suboperation`` also calls ``helper`` which creates nested\ninjection scope, as in the previous example.\n\nSo, as you can see, one of the main benefits of the library is prefix\ntransmission between separated functions. In couple with prefix management,\nthis keeps API of your functions and their bodies clean, saves your time and\nmental focus.\n\n\nLogger wrapper\n--------------\n\n``isotopic_logging`` allows you to wrap your loggers to prevent you from typing\n``inj.mark()`` every time you put some message to log. This saves space for\ncode and makes it more readable.\n\nWrapping is done via ``isotopic_logging.IsotopicLogger`` logger wrapper. It\nwraps loggers which are instances of ``logging.Logger`` and its subclasses.\n\nWrapper provides methods for creation of logger proxies with predefined prefix\ninjectors:\n\n- ``direct()`` for ``DirectPrefixInjector``;\n- ``static()`` for ``StaticPrefixInjector``;\n- ``auto()`` for ``AutoprefixInjector``;\n- ``hybrid()`` for ``HybridPrefixInjector``.\n\nThese methods accept same parameters as proper injection context managers. They\nreturn contex managers for getting logger proxies. Proxies act as usual loggers\nand they wrap logging calls with specific prefix.\n\nExample:\n\n.. code-block:: python\n\n import logging\n\n from isotopic_logging import IsotopicLogger\n\n LOG = IsotopicLogger(logging.getLogger(__name__))\n\n with LOG.auto() as log:\n log.debug(\"debug message\")\n log.info(\"info message\")\n log.warning(\"warning message\")\n log.error(\"error message\")\n log.critical(\"critical message\")\n\n # DEBUG [2015-12-31 13:38:55,554] 4B9FB5 | debug message\n # INFO [2015-12-31 13:38:55,554] 4B9FB5 | info message\n # WARNING [2015-12-31 13:38:55,554] 4B9FB5 | warning message\n # ERROR [2015-12-31 13:38:55,554] 4B9FB5 | error message\n # CRITICAL [2015-12-31 13:38:55,554] 4B9FB5 | critical message\n\nHere, ``LOG.auto()`` produces context which creates logger proxy with injected\nautoprefix.\n\n\nTime tracking\n-------------\n\nPrefix injectors allow you to track execution time within scopes. They provide:\n\n- ``elapsed_time`` attribute, which counts elapsed_time in seconds;\n- ``format_elapsed_time()`` method, which can accept custom format to output\n elapsed time as a string.\n\nExamples:\n\n.. code-block:: python\n\n import time\n from isotopic_logging import auto_injector\n\n with auto_injector() as inj:\n time.sleep(0.1)\n print(inj.elapsed_time)\n\n # 0.105129003525\n\nNested and inherited scopes have own internal time tracking:\n\n.. code-block:: python\n\n with auto_injector() as inj1:\n time.sleep(0.1)\n\n with auto_injector() as inj2:\n time.sleep(0.1)\n print(\"inj2\", inj2.elapsed_time)\n\n print(\"inj1\", inj1.elapsed_time)\n\n # ('inj2', 0.10514497756958008)\n # ('inj1', 0.2101149559020996)\n\nDefault formatting outputs hours, minutes, seconds and microseconds:\n\n.. code-block:: python\n\n with auto_injector() as inj:\n time.sleep(0.1)\n print(inj.format_elapsed_time())\n\n # 00:00:00.105154\n\nYou can use custom format compatible with format of\n``datetime.datetime.strftime()``:\n\n.. code-block:: python\n\n format = \"%H/%M/%S\"\n\n with auto_injector() as inj:\n time.sleep(5)\n print(inj.format_elapsed_time(format))\n\n # 00/00/05\n\n\nInterthread prefix transmission\n-------------------------------\n\nSometimes you may need to pass operation prefix between threads or processes.\nFor example, you start operation by handling HTTP request and continue it in\na background worker.\n\nThis can be easily made by using injector's ``prefix`` attribute and\n``DirectPrefixInjector``:\n\n.. code-block:: python\n\n def suboperation_in_another_thread_or_process(parent_prefix):\n with direct_injector(parent_prefix) as inj:\n print(inj.mark(\"foo\"))\n\n def operation():\n with auto_injector() as inj:\n print(inj.mark(\"foo\"))\n suboperation_in_another_thread_or_process(inj.prefix)\n\n operation()\n\n # 3539DB | foo\n # 3539DB | foo\n\n\nChangelog\n---------\n\n* `2.0.0`_ (Dec 31, 2015)\n\n * Feature: support inherited prefixes (`issue #1`_).\n * Feature: simple and clean way to inject prefixes into calls to existing\n loggers (`issue #4`_).\n * Feature: ability to get context execution time (`issue #3`_).\n * Optimization: instances of injectors will be created only if new scope is\n defined (`issue #5`_).\n * Improvement: ensure prefix and target message are converted to strings\n during concatenation.\n * Renamings:\n\n - ``prefix_injector`` to ``static_injector``;\n - ``autoprefix_injector`` to ``auto_injector``;\n\n *Old names are preserved and still can be used*.\n * Reduction: remove optional ``container`` parameter from everywhere.\n\n* `1.0.1`_ (Jul 30, 2015)\n\n * Fix: threading support for ``default_oid_generator`` which is used by\n default by ``autoprefix_injector`` and ``hybrid_injector`` (`issue #2`_).\n\n* `1.0.0`_ (May 3, 2015)\n\n Initial version\n\n\n.. |pypi_package| image:: http://img.shields.io/pypi/v/isotopic-logging.svg?style=flat\n :target: http://badge.fury.io/py/isotopic-logging/\n :alt: Latest PyPI package\n\n.. |pypi_downloads| image:: http://img.shields.io/pypi/dm/isotopic-logging.svg?style=flat\n :target: https://crate.io/packages/isotopic-logging/\n :alt: Downloands of latest PyPI package\n\n.. |python_versions| image:: https://img.shields.io/badge/Python-2.7,3.4-brightgreen.svg?style=flat\n :alt: Supported versions of Python\n\n.. |license| image:: https://img.shields.io/badge/license-LGPLv3-blue.svg?style=flat\n :target: https://github.com/oblalex/isotopic-logging/blob/master/LICENSE\n\n.. |unix_build| image:: http://img.shields.io/travis/oblalex/isotopic-logging.svg?style=flat&branch=master\n :target: https://travis-ci.org/oblalex/isotopic-logging\n :alt: Build status of the master branch on Unix\n\n.. |windows_build| image:: https://ci.appveyor.com/api/projects/status/hopk502wokd0qdyb/branch/master?svg=true\n :target: https://ci.appveyor.com/project/oblalex/isotopic-logging\n :alt: Build status of the master branch on Windows\n\n.. |coverage_status| image:: http://codecov.io/github/oblalex/isotopic-logging/coverage.svg?branch=master\n :target: http://codecov.io/github/oblalex/isotopic-logging?branch=master\n :alt: Test coverage\n\n.. |code_issues| image:: https://www.quantifiedcode.com/api/v1/project/c5eb11f66c184f679d30b3e1b883ae6c/badge.svg\n :target: https://www.quantifiedcode.com/app/project/c5eb11f66c184f679d30b3e1b883ae6c\n :alt: Code issues\n\n.. |codeclimate| image:: https://codeclimate.com/github/oblalex/isotopic-logging/badges/gpa.svg\n :target: https://codeclimate.com/github/oblalex/isotopic-logging\n :alt: Code Climate\n\n.. |codacy| image:: https://api.codacy.com/project/badge/grade/802f334a292f45b2898d8777ad46b611\n :target: https://www.codacy.com/app/oblalex/isotopic-logging\n :alt: Codacy Code Review\n\n.. |quality| image:: https://scrutinizer-ci.com/g/oblalex/isotopic-logging/badges/quality-score.png?b=master&style=flat\n :target: https://scrutinizer-ci.com/g/oblalex/isotopic-logging/?branch=master\n :alt: Scrutinizer Code Quality\n\n.. |health| image:: https://landscape.io/github/oblalex/isotopic-logging/master/landscape.svg?style=flat\n :target: https://landscape.io/github/oblalex/isotopic-logging/master\n :alt: Code Health\n\n.. |requirements| image:: https://requires.io/github/oblalex/isotopic-logging/requirements.svg?branch=master\n :target: https://requires.io/github/oblalex/isotopic-logging/requirements/?branch=master\n :alt: Requirements Status\n\n\n.. _Cheese Shop: https://pypi.python.org/pypi/isotopic-logging\n.. _Isotopic labeling: http://en.wikipedia.org/wiki/Isotopic_labeling\n\n\n.. _2.0.0: https://github.com/oblalex/isotopic-logging/compare/v1.0.1...v2.0.0\n.. _1.0.1: https://github.com/oblalex/isotopic-logging/compare/v1.0.0...v1.0.1\n.. _1.0.0: https://github.com/oblalex/isotopic-logging/releases/tag/v1.0.0\n\n\n.. _issue #1: https://github.com/oblalex/isotopic-logging/issues/1\n.. _issue #2: https://github.com/oblalex/isotopic-logging/issues/2\n.. _issue #3: https://github.com/oblalex/isotopic-logging/issues/3\n.. _issue #4: https://github.com/oblalex/isotopic-logging/issues/4\n.. _issue #5: https://github.com/oblalex/isotopic-logging/issues/5", "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/oblalex/isotopic-logging", "keywords": "library,logging", "license": "LGPLv3", "maintainer": null, "maintainer_email": null, "name": "isotopic-logging", "package_url": "https://pypi.org/project/isotopic-logging/", "platform": "any", "project_url": "https://pypi.org/project/isotopic-logging/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/oblalex/isotopic-logging" }, "release_url": "https://pypi.org/project/isotopic-logging/2.0.0/", "requires_dist": null, "requires_python": null, "summary": "Mark and trace events in your log alike isotopic labeling", "version": "2.0.0" }, "last_serial": 1883597, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "40ace9821c06617b31a61694b0eb4b00", "sha256": "1553244a0403d1d6c81319fe6581b4baa4c520beaa493723036861d290721bc4" }, "downloads": -1, "filename": "isotopic-logging-1.0.0.tar.gz", "has_sig": false, "md5_digest": "40ace9821c06617b31a61694b0eb4b00", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13571, "upload_time": "2015-05-03T15:18:20", "url": "https://files.pythonhosted.org/packages/50/26/36067a582fa90fb7858550f251d521ab864663bd4cd40d0b2cd9fb3a32c6/isotopic-logging-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "acd73ee14423924772fa71e08ae263a1", "sha256": "8f65f24373d7c26dbf9a3daae5cd5b3a2ae059cae15cd62b72ccb6b84ae1c6cf" }, "downloads": -1, "filename": "isotopic-logging-1.0.1.tar.gz", "has_sig": false, "md5_digest": "acd73ee14423924772fa71e08ae263a1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16635, "upload_time": "2015-07-29T21:01:44", "url": "https://files.pythonhosted.org/packages/41/be/3d5d848e6c6e0e1860601abc07ca5785c04fd4e88268874a46cb5e09361c/isotopic-logging-1.0.1.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "3a2d6b70c7ec19a1ddb7b8404e67c6c9", "sha256": "2a64485a6ff1bc22f78c343e0484df11667a9aed4f4be4e1a540800dc468e81a" }, "downloads": -1, "filename": "isotopic-logging-2.0.0.tar.gz", "has_sig": false, "md5_digest": "3a2d6b70c7ec19a1ddb7b8404e67c6c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21381, "upload_time": "2015-12-31T15:13:01", "url": "https://files.pythonhosted.org/packages/35/a4/4fea051b3de7bf71d4f18a6ff68c2b9bf9664cc5113d818665e0289180e7/isotopic-logging-2.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "3a2d6b70c7ec19a1ddb7b8404e67c6c9", "sha256": "2a64485a6ff1bc22f78c343e0484df11667a9aed4f4be4e1a540800dc468e81a" }, "downloads": -1, "filename": "isotopic-logging-2.0.0.tar.gz", "has_sig": false, "md5_digest": "3a2d6b70c7ec19a1ddb7b8404e67c6c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21381, "upload_time": "2015-12-31T15:13:01", "url": "https://files.pythonhosted.org/packages/35/a4/4fea051b3de7bf71d4f18a6ff68c2b9bf9664cc5113d818665e0289180e7/isotopic-logging-2.0.0.tar.gz" } ] }