{ "info": { "author": "Kevin L. Mitchell", "author_email": "klmitch@mit.edu", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: User Interfaces" ], "description": "============================\nCommand Line Interface Tools\n============================\n\n.. image:: https://travis-ci.org/klmitch/cli_tools.svg?branch=master\n :target: https://travis-ci.org/klmitch/cli_tools\n\nThe command line interface tools module provides several decorators\nwhich can be applied to a regular function to turn it into a console\nscript. It is designed to adapt a function so that it can be used as\na ``console_scripts`` entrypoint. The decorators allow various\ncommand line arguments to be declared, and for the command line to be\nparsed using the ``argparse`` module; the results of the parsing are\nthen passed to the function as regular keyword arguments. This does\nnot interfere with the normal calling conventions of the function; it\ncan be called from Python code directly.\n\nSimple usage of ``cli_tools``\n=============================\n\nThe simplest example of using the ``cli_tools`` decorators is as\nfollows::\n\n from cli_tools import *\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\nIn this declaration, the function is defined as taking no arguments\n(except ``argparse``'s default of \"--help\"). The description of the\nresulting script will be \"Performs an action.\"\n\nTo declare this as an actual console script, the following clause will\nneed to be added to the ``setup()`` call in your setup.py::\n\n entry_points={\n 'console_scripts': [\n 'function = your_module:function.console',\n ],\n }\n\nNotice in particular the \".console\" appended to the function name.\nThe decorators add several attributes to the function, including the\ncallable ``console()``, which performs the actual command line\nargument parsing.\n\nThe above example is the simplest example, but it would be more\ninteresting with some defined arguments::\n\n @argument('--debug', '-d',\n dest='debug',\n action='store_true',\n default=False,\n help=\"Run the tool in debug mode.\")\n @argument('--dryrun', '--dry_run', '--dry-run', '-n',\n dest='dry_run',\n action='store_true',\n default=False,\n help=\"Perform a dry run.\")\n def function(dry_run=False):\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\nThe first thing to notice is the elimination of the ``@console``\ndecorator. It doesn't hurt anything to use ``@console``, but all the\ndecorators perform the same core actions; as long as one of the other\ndecorators is used, ``@console`` is unnecessary.\n\nThe second thing to notice is that the ``dest`` specified for the\n\"--dryrun\" option matches the only function argument. When run as a\nconsole script, the value computed from the command line arguments\nwill be passed as this keyword parameter.\n\nThe third thing to notice is that the ``dest`` specified for the\n\"--debug\" option matches no function arguments. That flag will simply\nnot be passed to the function.\n\n(As it happens, the ``debug`` argument is treated specially. Under\nnormal circumstances, if the function raises an exception, the\nexception is coerced to a string, printed to standard error, and then\nthe console script exits. If the ``debug`` argument is ``True``,\nhowever, the exception will not be caught, resulting in a print out of\nthe stack trace.)\n\nGetting a Little More Advanced: Set the Description\n===================================================\n\nBy default, the first paragraph of the function docstring becomes the\ndescription for the console script, which is printed out when the\n\"--help\" option is given. This and several other ``argparse`` options\nmay be overridden using the following decorators:\n\n@prog()\n Overrides the ``prog`` parameter passed to\n ``argparse.ArgumentParser``. By default, it will be ``None``,\n causing the program name to be derived from ``sys.argv[0]``.\n\n@usage()\n Overrides the ``usage`` parameter passed to\n ``argparse.ArgumentParser``. By default, it will be ``None``,\n causing the usage message to be automatically computed by\n ``argparse``.\n\n@description()\n Overrides the ``description`` parameter passed to\n ``argparse.ArgumentParser``. By default, it will be the first\n paragraph of the function docstring.\n\n@epilog()\n Overrides the ``epilog`` parameter passed to\n ``argparse.ArgumentParser``. By default, it will be ``None``; when\n given, the text will be output at the end of the help text.\n\n@formatter_class()\n Overrides the ``formatter_class`` parameter passed to\n ``argparse.ArgumentParser``. By default, it will be\n ``argparse.HelpFormatter``. See the ``argparse`` documentation for\n more details.\n\nGetting More Advanced: Argument Groups\n======================================\n\nThe ``argparse`` package provides the ability to group arguments.\nThere are two ways of grouping arguments; in the first, related\narguments are simply grouped together so their documentation is more\neasily found, while in the second, a group of arguments are identified\nas mutually exclusive. The ``cli_tools`` decorators accommodate this\nby adding a special ``group`` parameter to the ``@argument()``\ndecorator; this group name identifies a group added using the\n``@argument_group()`` or ``@mutually_exclusive_group()`` decorators,\nand must be unique. These latter two decorators take the group name\nas the first argument, and remaining keyword arguments are passed to\nthe underlying ``argparse.ArgumentParser.add_argument_group()`` and\n``argparse.ArgumentParser.add_mutually_exclusive_group()`` methods.\n\nArgument Declaration Order\n==========================\n\nArguments and groups are constructed in the order in which they appear\nin the file; that is, in the earlier example, the \"--debug\" option\nwill be added to the argument parser before the \"--dryrun\" option.\nThis is opposite the normal decorator rules, but simplifies setting up\nthe arguments, particularly positional arguments.\n\nProcessors\n==========\n\nSome functions can't act as stand-alone console scripts without some\nsort of setup. For instance, it may be necessary to configure\nlogging. This can be handled by declaring a processor::\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\n @function.processor\n def _processor(args):\n logging.basicConfig()\n\nHere we declare the function ``_processor()`` as a processor for the\nconsole script ``function()``. After the command line arguments are\nparsed, ``_processor()`` will be called with those arguments; after it\nreturns, ``function()`` will be called.\n\nIt is also possible to perform actions after the function returns.\nConsider the following example::\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n return result\n\n @function.processor\n def _processor(args):\n result = yield\n print result\n yield None\n\nHere we turn ``_processor()`` into a generator; the result of the\nfirst ``yield`` statement is the return value of ``function()``, which\nwe can see will be whatever result it computed. Thus,\n``_processor()`` will print out that result, then yield ``None``--this\nis needed so that the script exits without any errors; a non-``None``\nvalue is interpreted as an error condition by the machinery\nsurrounding the ``console_scripts`` endpoint.\n\nGenerator-based processors also receive any exceptions thrown by the\nfunction, like so::\n\n class BailoutException(Exception):\n pass\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n raise BailoutException(\"I'm done\")\n\n @function.processor\n def _processor(args):\n try:\n result = yield\n except BailoutException:\n print \"All done!\"\n else:\n print \"Results so far: %s\" % result\n yield None\n\nNote the ``try`` block around the first ``yield``, which allows the\nprocessor function to catch this special exception and do something\nappropriate.\n\nArgument Hooks\n==============\n\nIt may be necessary to arbitrarily manipulate the argument parser\nbefore parsing the command line arguments. For instance, a system\nwhich used pluggable authentication modules may need to allow those\nmodules to add specific command line arguments. This can be handled\nby declaring an argument hook::\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\n @function.args_hook\n def _hook(parser):\n parser.add_argument(...)\n\nHere we declare the function ``_hook()`` as an argument hook for the\nconsole script ``function()``. After the declared arguments have been\nadded to the parser, the hook will be called with the parser (an\n``argparse.ArgumentParser`` instance), which it can manipulate in any\nway.\n\nIt is also possible to manipulate the parser prior to adding the\ndeclared arguments. Consider the following example::\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\n @function.args_hook\n def _hook(parser):\n parser.add_argument(...)\n yield\n\nHere we turn ``_hook()`` into a generator. The statements preceding\nthe first ``yield`` statement will be run immediately before adding\nthe declared arguments, and can manipulate the parser in any way\nnecessary. If manipulation needs to be done after the declared\narguments are added, that can be done in statements following the\n``yield`` statement.\n\nAdvanced ``cli_tools`` Usage\n============================\n\nThe ``console()`` function added to the decorated function uses\nseveral other functions for setting up the argument parser\n(``setup_args()``), building the keyword arguments to pass to the\nunderlying function (``get_kwargs()``), and safely calling the\nfunction and handling exceptions (``safe_call()``). These functions\nare provided to allow other consumers to make use of the argument\ninformation. This could be used to build a \"Swiss army knife\" command\ninterpreter, for instance.\n\nIn fact, such \"Swiss army knife\" command interpreters are supported\ndirectly by ``cli_tools``, through the use of such decorators as\n``@subparsers()``, ``@load_subcommands()``, and the ``@subcommand()``\nargument parser decorator.\n\nWe begin by showing how to directly declare one function as a\nsubcommand of another::\n\n @console\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n pass\n\n @function.subcommand\n def subcmd1():\n \"\"\"\n Performs subcmd1.\n \"\"\"\n ...\n\n @function.subcommand('sub2')\n def subcmd2():\n \"\"\"\n Performs sub2.\n \"\"\"\n ...\n\nIn this example, we have defined two subcommands. The subcommand\ndefined by ``subcmd1()`` has a name derived from the function name,\nwhile the subcommand defined by ``subcmd2()`` has its name explicitly\nset to \"sub2\".\n\nTo introspect the declared subcommands, use the ``get_subcommands()``\nfunction which is also added to the decorated function. The\n``get_subcommands()`` function returns a dictionary mapping the\nsubcommand name to the function which implements that subcommand. For\ninstance, in the example above, ``function.get_subcommands()`` would\nreturn the dictionary ``{\"subcmd1\": subcmd1, \"sub2\": subcmd2}``.\n\nNote that, when using subcommands, the original function will never be\ncalled. If no subcommand is passed on the command line, the\nunderlying ``argparse`` module reports an error.\n\nIt is also possible to load subcommands using a ``pkg_resources``\nentrypoint group, using the ``@load_subcommands()`` decorator like\nso::\n\n @load_subcommands('example.subcommands')\n def function():\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\nIn this example, all functions listed under the \"example.subcommands\"\nentrypoint group will be added as subcommands of ``function()``, with\nthe subcommand name being set to the name of the entrypoint. For\ninstance, if the following entrypoint entries existed::\n\n entry_points={\n 'example.subcommands': [\n 'subcmd1 = your_module:subcommand1',\n 'subcmd2 = your_module:subcommand2',\n 'subcmd3 = other_module:subcommand3',\n ],\n }\n\nThen in the example above, the three subcommands \"subcmd1\", \"subcmd2\",\nand \"subcmd3\" would be defined. (Carefully note that these\nentrypoints are *not* followed by the \".console\" that was required in\nthe \"console_scripts\" entrypoint.)\n\nAs a final point, subcommands are handled by calling the\n``argparse.ArgumentParser.add_subparsers()`` method. This method can\ntake certain keyword arguments for nicer rendering of the help text;\nto set these arguments, use the ``@subparsers()`` decorator::\n\n @subparsers(title=\"My subcommands\")\n def function():\n \"\"\"\n Perform an action.\n \"\"\"\n ...\n\nArgument Completion\n===================\n\nThe command line interface tools module does not provide any\nintegration with shell argument completion directly. However,\n``cli_tools`` uses the ``argparse`` module, and any argument\ncompletion framework that works with ``argparse`` can be used with it.\nAs an example, consider the ``argcomplete`` module; here's an example\nof how it might be integrated into a ``cli_tools``-compatible CLI::\n\n from cli_tools import *\n import argcomplete\n\n # PYTHON_ARGCOMPLETE_OK\n\n @argument('--debug', '-d',\n dest='debug',\n action='store_true',\n default=False,\n help=\"Run the tool in debug mode.\")\n @argument('--dryrun', '--dry_run', '--dry-run', '-n',\n dest='dry_run',\n action='store_true',\n default=False,\n help=\"Perform a dry run.\")\n def function(dry_run=False):\n \"\"\"\n Performs an action.\n \"\"\"\n ...\n\n @function.args_hook\n def _hook(parser):\n argcomplete.autocomplete(parser)\n\nNote the use of an argument hook to invoke the\n``argcomplete.autocomplete()`` function; for ``argcomplete``, this\nperforms the actual argument completion. Also note the comment\ncontaining ``PYTHON_ARGCOMPLETE_OK``, which enables ``argcomplete``'s\nglobal completion mode.\n\nFor more information about ``argcomplete``, see:\n\n https://pypi.python.org/pypi/argcomplete\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/klmitch/cli_tools", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "cli_tools", "package_url": "https://pypi.org/project/cli_tools/", "platform": "", "project_url": "https://pypi.org/project/cli_tools/", "project_urls": { "Homepage": "https://github.com/klmitch/cli_tools" }, "release_url": "https://pypi.org/project/cli_tools/1.0.1/", "requires_dist": null, "requires_python": "", "summary": "Command Line Interface Tools", "version": "1.0.1" }, "last_serial": 5196679, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "ec63a9f0368deb37ae56ed7fd7c3d88d", "sha256": "eca27d36a70626f1468f91876c53919555fda767fc01a17656138c7461adcf90" }, "downloads": -1, "filename": "cli_tools-0.1.0.tar.gz", "has_sig": false, "md5_digest": "ec63a9f0368deb37ae56ed7fd7c3d88d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25130, "upload_time": "2013-03-16T05:47:58", "url": "https://files.pythonhosted.org/packages/07/a2/496ad56c17e4e9c7c0ff13e857be67b0d193e7142d102c9f9281b763bc99/cli_tools-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "15c668247a09298654f53a4cde802142", "sha256": "9a9ad6a2d9feec3203fb691cc4ce81cf058446d0030c0c180d5de49cae2dfe8f" }, "downloads": -1, "filename": "cli_tools-0.1.1.tar.gz", "has_sig": false, "md5_digest": "15c668247a09298654f53a4cde802142", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28662, "upload_time": "2013-03-16T07:27:11", "url": "https://files.pythonhosted.org/packages/12/22/56d7ce3244104411583cea9ed528fd8315a0f0cfdf30eeb80e68d694bf98/cli_tools-0.1.1.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "700055270f9f3f5625ac747686752f3b", "sha256": "57da47b8bfd563aa165b3c0b6f7fd3127cc1325b35363585b97e335d46c7e916" }, "downloads": -1, "filename": "cli_tools-0.2.0.tar.gz", "has_sig": false, "md5_digest": "700055270f9f3f5625ac747686752f3b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32200, "upload_time": "2013-03-16T15:50:32", "url": "https://files.pythonhosted.org/packages/ed/4e/9e2e3b1d1bed1002ccb58df5a67604f24aa63264129df6ac359f66668ed8/cli_tools-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "c38978b8fedb2fccd0649b9a06d39712", "sha256": "c3158a52a793e45d291abc1f47c0119b020ef2a3665baeb3e243ea93382453e4" }, "downloads": -1, "filename": "cli_tools-0.2.1.tar.gz", "has_sig": false, "md5_digest": "c38978b8fedb2fccd0649b9a06d39712", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32213, "upload_time": "2013-04-02T22:38:55", "url": "https://files.pythonhosted.org/packages/76/54/40e82ac867cf28076e48fe553d598364547d0ee46733817d0f6a03536b35/cli_tools-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "f0bf72109396ceca1ca985050a182c93", "sha256": "7f4b39229b19c584175ec3fe3587fa14861edcf0018a1594c45e80aa876d083d" }, "downloads": -1, "filename": "cli_tools-0.2.2.tar.gz", "has_sig": false, "md5_digest": "f0bf72109396ceca1ca985050a182c93", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28497, "upload_time": "2013-09-10T23:36:42", "url": "https://files.pythonhosted.org/packages/79/cb/ad51fc217a76827d0efab2c3f66854c1ad37f5b3d9f11f3448a5a76ffa34/cli_tools-0.2.2.tar.gz" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "caf1c88d02efc50c6cd5894f7308bf5e", "sha256": "85db1bd298ed322e4eda640bf15068dd3244458648e1a569be4bee40e457e7c2" }, "downloads": -1, "filename": "cli_tools-0.2.3.tar.gz", "has_sig": false, "md5_digest": "caf1c88d02efc50c6cd5894f7308bf5e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33771, "upload_time": "2014-03-15T13:36:04", "url": "https://files.pythonhosted.org/packages/2a/68/7bd1f90df99c1a92e47e12e4eaf7f600f0b46664c4192f90e35ec89df983/cli_tools-0.2.3.tar.gz" } ], "0.2.4": [ { "comment_text": "", "digests": { "md5": "2c50bfffbe8073a5d77cd6c4ac1b557e", "sha256": "ed3de16c357942b3e04d69f233c752df9e6e5217100ed3ab3f19663c83148d83" }, "downloads": -1, "filename": "cli_tools-0.2.4.tar.gz", "has_sig": false, "md5_digest": "2c50bfffbe8073a5d77cd6c4ac1b557e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33753, "upload_time": "2014-03-15T13:37:33", "url": "https://files.pythonhosted.org/packages/9f/0c/72d755843ceffcd12fd6e0e4efb0f21476333eb40a5e647f407f4ddb3882/cli_tools-0.2.4.tar.gz" } ], "0.2.5": [ { "comment_text": "", "digests": { "md5": "d0afd3707f0c5961c27f275925b23618", "sha256": "c29af23877e3143e31c7398043181314fb3592f30bcf42c444a80206c0a8fb6b" }, "downloads": -1, "filename": "cli_tools-0.2.5.tar.gz", "has_sig": false, "md5_digest": "d0afd3707f0c5961c27f275925b23618", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31079, "upload_time": "2014-03-18T16:07:47", "url": "https://files.pythonhosted.org/packages/b7/77/f75299c794a1397dcfa16fa7185c8b26f8fe3149f11a6f959a8272f12920/cli_tools-0.2.5.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "2c90dc3e43293dd303a7a6cea43ef1fa", "sha256": "e54edfe72e5da89a793444de7be1ac0e9b18f47c3427c324fe28194a7ab90406" }, "downloads": -1, "filename": "cli_tools-0.3.0.tar.gz", "has_sig": false, "md5_digest": "2c90dc3e43293dd303a7a6cea43ef1fa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38483, "upload_time": "2015-04-21T20:09:28", "url": "https://files.pythonhosted.org/packages/ef/db/e08127f1c366c55d825fea8a4eebdb6268ab202252f6a9c2fb4368598c40/cli_tools-0.3.0.tar.gz" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "af056558f1b627c25d522eb7b168a443", "sha256": "b333e248d7c80ceeaaf1cf91eac15f1fe37a45ed5e916485b4ad30c557ebc615" }, "downloads": -1, "filename": "cli_tools-0.4.0.tar.gz", "has_sig": false, "md5_digest": "af056558f1b627c25d522eb7b168a443", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38466, "upload_time": "2016-01-20T16:27:33", "url": "https://files.pythonhosted.org/packages/67/b3/b55d76e4c5a12f7f92ee84e1eb4b495adea7032372eafbff014356d16f40/cli_tools-0.4.0.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "05cd6bc0f97a9bec3c66a3dc5304a9e1", "sha256": "d3a32edf375b6185cd28cd154b3ea60714a97a2773425f19a2110d2efa296366" }, "downloads": -1, "filename": "cli_tools-0.4.1.tar.gz", "has_sig": false, "md5_digest": "05cd6bc0f97a9bec3c66a3dc5304a9e1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 38655, "upload_time": "2016-02-12T05:32:08", "url": "https://files.pythonhosted.org/packages/fe/46/f53369e70a296239a19b6f30059bd6f7a763e00f9f7c0c9434612bdc0b72/cli_tools-0.4.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "f5db06c7550566be197c7ea0c9c51378", "sha256": "4628f99bb912651bfa9949386bf295e8db826079c2b071f76908a7a3f0ce3afc" }, "downloads": -1, "filename": "cli_tools-1.0.0-py2-none-any.whl", "has_sig": false, "md5_digest": "f5db06c7550566be197c7ea0c9c51378", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 17920, "upload_time": "2017-01-21T19:19:13", "url": "https://files.pythonhosted.org/packages/5d/7e/e673c43fd6fe4bf27b7e95985cc4e662f61ce7b818f89f9a16aefda218c7/cli_tools-1.0.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ddd511fb0413cd24956d61fd57c0b2d8", "sha256": "c07bf1b32940cf6cb7b7203612687fcc3dc79deeb3fa5a29bd7fc9f12c7f832e" }, "downloads": -1, "filename": "cli_tools-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "ddd511fb0413cd24956d61fd57c0b2d8", "packagetype": "bdist_wheel", "python_version": "3.3", "requires_python": null, "size": 17921, "upload_time": "2017-01-21T19:29:22", "url": "https://files.pythonhosted.org/packages/1c/eb/26fd7a29f21a8bdd1c981a1d91d46a5e91061af65d7dc4dd4e70e6823935/cli_tools-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c16a63b3f9ee51c6b751d1903e6fc24f", "sha256": "72a4f606a8c7d03af5fc55a4cde445e1ac05fd89a325a38c1f4706b18cb46e9b" }, "downloads": -1, "filename": "cli_tools-1.0.0.tar.gz", "has_sig": false, "md5_digest": "c16a63b3f9ee51c6b751d1903e6fc24f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29959, "upload_time": "2017-01-21T19:05:49", "url": "https://files.pythonhosted.org/packages/26/8a/fffb2baa2d41c896392cedf3e0ab17864fa12386955e4f3f294f42caca48/cli_tools-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "b2784462a7ffd84a85f6d87b8f8ca8d4", "sha256": "769e09cf976918e17f76033511e8a47fb3c2732129141e41ba0448a1e78e08ea" }, "downloads": -1, "filename": "cli_tools-1.0.1.tar.gz", "has_sig": false, "md5_digest": "b2784462a7ffd84a85f6d87b8f8ca8d4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27556, "upload_time": "2019-04-27T13:39:58", "url": "https://files.pythonhosted.org/packages/49/68/55e3220ed46962654bd2db804cd19c30739b284b823a683d95946a114ec5/cli_tools-1.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "b2784462a7ffd84a85f6d87b8f8ca8d4", "sha256": "769e09cf976918e17f76033511e8a47fb3c2732129141e41ba0448a1e78e08ea" }, "downloads": -1, "filename": "cli_tools-1.0.1.tar.gz", "has_sig": false, "md5_digest": "b2784462a7ffd84a85f6d87b8f8ca8d4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27556, "upload_time": "2019-04-27T13:39:58", "url": "https://files.pythonhosted.org/packages/49/68/55e3220ed46962654bd2db804cd19c30739b284b823a683d95946a114ec5/cli_tools-1.0.1.tar.gz" } ] }