{ "info": { "author": "Jacob Morris", "author_email": "blendingjake@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Software Development :: Testing" ], "description": "# ExecTiming\n> An advanced timer for Python that makes it easy to determine execution times.\n\nPython has a built-in package named `timeit` which provides a way to\n\"Measure execution time of small code snippets.\" It can be great for quick\ntests, but lacks more expansive features like curve fitting, statistical information,\nand the ability to use in existing projects. ExecTiming seeks to change that by\nincluding most of the features of `timeit` and adding many more like decorators,\nargument calling and replacement, best-fit-curve determination, and in-project use.\n\n![Imgur Image](https://imgur.com/cJ62w1Z.png)\n\n## Features\n * `StaticTimer` which provides the ability to time functions via a decorator,\n strings, or just seeing the amount of time elapsed between a call to `.start_elapsed()` and `elapsed()`\n * `Timer` which gives a way to log measured times and then display statistical data,\n perform curve-fitting for functions with parameters as the independent variables and\n the measured time as the dependent variable, and all the features the StaticTimer has.\n * All output can be re-directed by changing `output_stream`, which can be any\n file-like object or anything with a `.write()` method.\n * A wrapper for `logging.info` and `logging.debug` is included to redirect\n output to those sources\n * Measured times can be displayed in seconds `s`, milliseconds `ms`,\n microseconds `us`, or nanoseconds `ns`.\n * The same block can be executed multiple times to get a more accurate reading.\n This is done by setting `iterations_per_run`. After that many iterations, the\n elapsed time is measured.\n * Multiple runs can be carried out and averaged to remove outlying results.\n\n# [Wiki](https://github.com/BlendingJake/ExecTiming/wiki)\n\n## Glossary\n * [`Installation`](#install)\n * [`Decorator`](#decorate)\n * [`Time Strings`](#time_it)\n * [`Statistics & Best Fit Curve`](#statistics_best_fit)\n * [`Plotting`](#plotting)\n\n### Installation\n```\npip install exectiming\n```\nFor full functionality:\n\n * `scipy` - for best-fit-curve\n * `scikit-learn` - for best-fit-curve\n * `numpy` - for best-fit-curve\n * `matplotlib` - for plotting\n\nHowever, basic functionality will still exist even if those dependencies aren't found\n\n\n### Static decorate\n```python\nfrom exectiming.exectiming import StaticTimer\nfrom random import randint\n\n@StaticTimer.decorate(runs=5, average_runs=False, call_callable_args=True, log_arguments=True)\ndef factorial(n):\n if n == 1:\n return 1\n else:\n return n * factorial.__wrapped__(n-1)\n\nfactorial(lambda: randint(3, 40))\n# 0.01663 ms - factorial(33) ... [runs= 1, iterations= 1] | Run 1\n# 0.00544 ms - factorial(19) ... [runs= 1, iterations= 1] | Run 2\n# 0.00736 ms - factorial(28) ... [runs= 1, iterations= 1] | Run 3\n# 0.00448 ms - factorial(17) ... [runs= 1, iterations= 1] | Run 4\n# 0.01087 ms - factorial(38) ... [runs= 1, iterations= 1] | Run 5\n```\n\n * Functions, even recursive ones, can be wrapped so that any time they are called they get timed\n * You can replace arguments with functions which can be automatically called and the argument replaced\n to allow testing like shown above, `call_callable_args=True`\n * Arguments show up in the output to provide more information, `log_arguments=True`\n\n### Static time_it\n```python\nfrom exectiming.exectiming import StaticTimer\n\nStaticTimer.time_it(\"pow(2, 64)\", runs=10, iterations_per_run=10000)\n# 107.10668 ms - pow(2, 64) ... [runs= 10, iterations=10000] \n\nStaticTimer.time_it(\"2**64\", runs=10, iterations_per_run=10000)\n# 68.75266 ms - 2**64 ... [runs= 10, iterations=10000] \n\nStaticTimer.time_it(\"1<<64\", runs=10, iterations_per_run=10000)\n# 65.53690 ms - 1<<64 ... [runs= 10, iterations=10000] \n```\n\n * Strings can be timed\n * Multiple runs can be measured then averaged together to get a more accurate result\n * Anything needed names can be passed in by setting `globals=`, `locals=`, or `setup=`. \n The first two must be maps of names to objects. The second is a string that is \n executed once because the string `block` is timed.\n\n`time_it` can be used to re-write the decorator above example like so:\n```python\nStaticTimer.time_it(factorial, lambda: randint(3, 40), call_callable_args=True, average_runs=False, runs=5, log_arguments=True)\n```\n\n### Assume\n```python\nfrom exectiming.exectiming import Timer\ntimer = Timer()\n```\n\n\n### Transformers, statistics, and best fit\n```python\n@timer.decorate(runs=5, log_arguments=True, call_callable_args=True)\ndef bubble_sort(array):\n while True:\n switched = False\n for i in range(0, len(array)-1):\n if array[i] > array[i+1]:\n array[i], array[i+1] = array[i+1], array[i]\n switched = True\n\n if not switched:\n break\n\n return array\n\nbubble_sort(lambda: [randint(0, 1000) for _ in range(randint(100, 5000))])\n\ntimer.output(split_index=\"bubble_sort\", transformers={0: len})\n# bubble_sort:\n# 1333.19493 ms - bubble_sort(2141) ... [runs= 1, iterations= 1] \n# 1413.75243 ms - bubble_sort(2546) ... [runs= 1, iterations= 1] \n# 4247.70385 ms - bubble_sort(4530) ... [runs= 1, iterations= 1] \n# 34.01533 ms - bubble_sort(421) ... [runs= 1, iterations= 1] \n# 675.07202 ms - bubble_sort(1752) ... [runs= 1, iterations= 1] \n\ntimer.statistics()\n# bubble_sort[runs=5, total=7703.73856 ms]:\n# Min | Max | Average = 34.01533 | 4247.70385 | 1540.74771 ms\n# Standard Deviation = 1442.66797 ms\n# Variance = 2081.29087 ms\n\nprint(timer.best_fit_curve(transformers=len))\n# ('Polynomial', {'a': 2.8042911992314363e-07, 'b': -9.670141667209306e-05, 'c': 0.024305228337961525})\n```\n\n * `Timer` stores the output until requested\n * Function parameters can be transformed in the output. In the above example,\n `transformers={0: len}` indicates that the positional argument at index `0` should have its\n value in the output replaced by the result of calling the function with that parameter. \n The output otherwise would have been something like `bubble_sort([1, 2, 3, 4, 5, ...])`\n * `transformers=len` is also valid as all of the arguments can be transformed with `len`, so \n there is no need to specify which index/key the function should be used for.\n * Basic statistics can be displayed\n * A best-fit-curve can be determined. To use this, all logged function parameters\n must be integers. In this case, one was an a list, so it was transformed so that the\n analysis was done on the length of the list, instead of the list itself.\n * The resulting best fit curve is, if `x=len(list)`, `y = ax^2 + bx + c`. \n We can extrapolate execution time using this curve to determine how long it would \n take to sort a list of length `x=10000`, which would be \n `0.0000002804*10000^2 - 0.0000967*10000 + 0.0243`. That is `27.10 s` or `27100 ms`\n\n### Plotting factorial\n```python\n@timer.decorate(runs=100, iterations_per_run=10, call_callable_args=True, log_arguments=True)\ndef factorial(n):\n if n == 1:\n return 1\n else:\n return n * factorial.__wrapped__(n-1)\n\nfactorial(lambda: randint(1, 100))\ntimer.plot(plot_curve=True, time_unit=timer.US, equation_rounding=5)\n```\n![Imgur Image](https://imgur.com/7G1mDnO.png)\n\n * `.plot()` provides a quick way to plot the measured times against an argument\n that is the independent variable\n * The best fit curve and equation can be automatically determined and added\n by setting `plot_curve=True`\n\n### Plotting bubble_sort\n```python\n# using bubble_sort from above, just with runs=10\n\nbubble_sort(lambda: [randint(0, 100) for _ in range(randint(100, 2500))])\ncurve = timer.best_fit_curve(transformers=len)\ntimer.plot(transformer=len, plot_curve=True, curve=curve, x_label=\"List Length\")\n```\n![Imgur Image](https://imgur.com/t2eZ0aN.png)\n\n * The curve can be determined beforehand and then passed into `plot()`\n * `plot()` needs `transformer=len` because the independent and dependent variables\n must be integers, so `len` is used to make it it one\n\n### Plotting binary_search\n```python\n@timer.decorate(runs=100, iterations_per_run=5, log_arguments=True, call_callable_args=True)\ndef binary_search(sorted_array, element):\n lower, upper = 0, len(sorted_array)\n middle = upper // 2\n\n while middle >= lower and middle != upper:\n if element == sorted_array[middle]:\n return middle\n elif element > sorted_array[middle]:\n lower = middle + 1 # lower must be beyond middle because the middle wasn't right\n else:\n upper = middle - 1 # upper must be lower than the middle because the middle wasn't right\n\n middle = (upper + lower) // 2\n\n return None # couldn't find it\n\nbinary_search(lambda: [i for i in range(randint(0, 10000))], lambda: randint(0, 10000))\ntimer.plot(plot_curve=True, curve=timer.best_fit_curve(exclude={1}, transformers=len), key=0,\n transformer=len, time_unit=timer.US, x_label=\"List Length\", equation_rounding=4,\n title=\"Binary Search - Random Size, Random Element\")\n```\n![Imgur Image](https://imgur.com/SeFfZHS.png)\n\n * `binary_search()` takes two arguments, so `best_fit_curve` is set to ignore\n the second one, at index 1, and to transform the argument at index 0 using `len`\n * Once the curve is determined, the split must be plotted. Again, there are\n two arguments, so `key=0` says to use the first as the independent variable and\n `transformer=len` will transform the list into an integer\n * Additionally, the title and x-axis labels are specified and rounding set lower\n * The equation displayed on the plot uses the timescale specified by `time_unit`\n\n### Plotting multiple splits\n```python\nfrom exectiming.exectiming import Timer\nfrom random import randint\n\n\ntimer = Timer()\n\n\ndef bubble_sort(array):\n while True:\n switched = False\n for i in range(0, len(array)-1):\n if array[i] > array[i+1]:\n array[i], array[i+1] = array[i+1], array[i]\n switched = True\n\n if not switched:\n break\n\n return array\n\n\ndef selection_sort(array):\n for i in range(len(array) - 1):\n # find min\n min_i = i\n for j in range(i+1, len(array)):\n if array[j] < array[min_i]:\n min_i = j\n\n # swap\n array[i], array[min_i] = array[min_i], array[i]\n\n return array\n\n\ntimer.time_it(bubble_sort, lambda: [randint(0, 1000) for _ in range(randint(100, 3000))], call_callable_args=True,\n log_arguments=True, runs=10)\ntimer.time_it(selection_sort, lambda: [randint(0, 1000) for _ in range(randint(100, 3000))], call_callable_args=True,\n log_arguments=True, runs=10)\ntimer.time_it(sorted, lambda: [randint(0, 1000) for _ in range(randint(100, 3000))], call_callable_args=True,\n log_arguments=True, runs=10)\n\n\nbubble_sort_curve = timer.best_fit_curve(\"bubble_sort\", transformers=len)\nselection_sort_curve = timer.best_fit_curve(\"selection_sort\", transformers=len)\nsorted_curve = timer.best_fit_curve(\"sorted\", transformers=len)\n\ntimer.plot(\"bubble_sort\", plot_curve=True, curve=bubble_sort_curve, multiple=True, transformer=len)\ntimer.plot(\"selection_sort\", plot_curve=True, curve=selection_sort_curve, multiple=True, transformer=len)\ntimer.plot(\"sorted\", plot_curve=True, curve=sorted_curve, title=\"Sorting Algorithms\", x_label=\"List Length\",\n transformer=len)\n\n```\n![Imgur Image](https://imgur.com/zm1p6jz.png)\n\n * Multiple splits and curves can be plotted by setting `multiple=True` for \n all but the last call to `.plot()`.\n * `title`, `x_label`, and `y_label` of the last call will be used\n\n## TODO\n - [x] Add parameter checking to `.plot()` and `.best_fit_curve()` to make sure \n arguments are integers to avoid difficult to decipher errors\n - [x] Change `.best_fit_curve()` to allow `transformers` to be a callable\n - [x] Change `.output()` to not require `split_index` if `transformers={0:len}`. \n Allow `transformers` to be just a function, if there is only one argument, or a map \n or a map of a map.\n - [x] Change `.sort_runs()` to reflect that values don't have to be integers, \n they just have to be comparable. If they aren't, then a transformer is needed. \n This change is mainly cosmetic. (BJ - nothing actually needed changed)\n - [x] Add `.predict(params, arguments)` to `Timer`. Should basically be a\n pass-through call to `.calculate_point()` on the correct best-fit-curve\n - [x] Collapse `exclude_args` and `exclude_kwargs` down into just `exclude`.\n The difference between positional and keyword arguments can be determined as\n int vs. str.\n - [x] Change how coefficients are returned for `BestFitLinear`, maybe use\n **xindex/key**\n - [x] Add context manager\n - [x] Make scipy, numpy, and scikit-learn optional, just prohibit `best_fit_curve` if they aren't there\n - [x] Add graphing feature with matplotlib, Linear will only be graphed if there is a single argument\n - [x] Add the ability to sort runs so they are display in some sort of order. Maybe allow sorting\n by time or by an argument\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/blendingjake/exectiming", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "exectiming", "package_url": "https://pypi.org/project/exectiming/", "platform": "", "project_url": "https://pypi.org/project/exectiming/", "project_urls": { "Homepage": "https://github.com/blendingjake/exectiming" }, "release_url": "https://pypi.org/project/exectiming/2.0.1/", "requires_dist": [ "scikit-learn", "scipy", "numpy", "matplotlib" ], "requires_python": ">=3.3", "summary": "A Python module for measuring the execution time of code", "version": "2.0.1" }, "last_serial": 5417856, "releases": { "2.0.0": [ { "comment_text": "", "digests": { "md5": "610053ca87669f99a07b5aaf557073de", "sha256": "6b5ccb6cd51939f687ea03001cb796d9996c45573047289aea62eea1f7f60212" }, "downloads": -1, "filename": "exectiming-2.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "610053ca87669f99a07b5aaf557073de", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.3", "size": 34522, "upload_time": "2019-06-08T03:02:34", "url": "https://files.pythonhosted.org/packages/5f/09/162dcf952f108b43cb9c9799910dd474aa1da6777b8dd4bba3f49aab5a5b/exectiming-2.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "cb68a145509a0b213229a30cd48fdcfb", "sha256": "17a60cdfb0523852237ef021f2f55f5c3f450c1516c1e02d7bc1d3e9c0fab937" }, "downloads": -1, "filename": "exectiming-2.0.0.tar.gz", "has_sig": false, "md5_digest": "cb68a145509a0b213229a30cd48fdcfb", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.3", "size": 23179, "upload_time": "2019-06-08T03:02:36", "url": "https://files.pythonhosted.org/packages/30/e8/fd12fffa279c2fd68ba812058a1993891796f49da0e89093d80c6b7ce6b0/exectiming-2.0.0.tar.gz" } ], "2.0.0rc1": [ { "comment_text": "", "digests": { "md5": "d03428879f3fe58673ccbe1d6d8b7ed7", "sha256": "64ee681c3b4b7fe98bf4df0474e5c703660457a41714a75f574c2e630f792f7c" }, "downloads": -1, "filename": "exectiming-2.0.0rc1-py3-none-any.whl", "has_sig": false, "md5_digest": "d03428879f3fe58673ccbe1d6d8b7ed7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 32836, "upload_time": "2019-06-05T19:23:53", "url": "https://files.pythonhosted.org/packages/94/d8/ade22e9b8ce700b0df73102de76d299c9134a8324e42287b1f85314cec65/exectiming-2.0.0rc1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e8d03f9d3e1bed4670ac7ec28515506a", "sha256": "c337e64066bc733f97119f52dc6b10d59499cc057ca3ff4d8afef5e98202bc97" }, "downloads": -1, "filename": "exectiming-2.0.0rc1.tar.gz", "has_sig": false, "md5_digest": "e8d03f9d3e1bed4670ac7ec28515506a", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 18550, "upload_time": "2019-06-05T19:23:56", "url": "https://files.pythonhosted.org/packages/83/e8/723565871d4558eb2536a52f56c866754cd1d4a94fcd0c779b77ae44a987/exectiming-2.0.0rc1.tar.gz" } ], "2.0.0rc2": [ { "comment_text": "", "digests": { "md5": "45e87e300f9f59d907e4078da1f85726", "sha256": "10e9a72179dc6e978904a267a851d65f3c4751c4b70f87c4aa3e66be5d67585b" }, "downloads": -1, "filename": "exectiming-2.0.0rc2-py3-none-any.whl", "has_sig": false, "md5_digest": "45e87e300f9f59d907e4078da1f85726", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 32810, "upload_time": "2019-06-05T19:27:07", "url": "https://files.pythonhosted.org/packages/8d/f2/df7ce3882d27450a0bc812f8c9695bf7da3dcd27dec8d74ce8988000d619/exectiming-2.0.0rc2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3a0001151ab4b0380a2420159f641791", "sha256": "7c4b71c48ff680b6a53d840f54216865e6f38fe19f6bda2f63c72c20f00788f5" }, "downloads": -1, "filename": "exectiming-2.0.0rc2.tar.gz", "has_sig": false, "md5_digest": "3a0001151ab4b0380a2420159f641791", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 18532, "upload_time": "2019-06-05T19:27:09", "url": "https://files.pythonhosted.org/packages/38/8d/4b53f71ec02baa82cf9aa2331e710ca9bba14689429671126c56312292be/exectiming-2.0.0rc2.tar.gz" } ], "2.0.0rc3": [ { "comment_text": "", "digests": { "md5": "88d09d32d7cebed2262d3cc0a838102d", "sha256": "8556c06ab6b5a9f804aa4e8c4b294762cd05b594a81ca98530080a131de2d0a4" }, "downloads": -1, "filename": "exectiming-2.0.0rc3-py3-none-any.whl", "has_sig": false, "md5_digest": "88d09d32d7cebed2262d3cc0a838102d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 32803, "upload_time": "2019-06-05T19:44:27", "url": "https://files.pythonhosted.org/packages/a2/28/378e3725af1b9500e64a237ba03ce4a65bd8c9643288c908f1e194afd9a6/exectiming-2.0.0rc3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0c9461f76b046f5e48bb82a43009daf3", "sha256": "9bdd10f6f30a29766ee16c7692b48288c75be5dcbea11503c1a62c9748969d25" }, "downloads": -1, "filename": "exectiming-2.0.0rc3.tar.gz", "has_sig": false, "md5_digest": "0c9461f76b046f5e48bb82a43009daf3", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 18443, "upload_time": "2019-06-05T19:44:28", "url": "https://files.pythonhosted.org/packages/3d/b4/30399a11462b91c99d814c25989deab45f28cfc7ea100fe4d67944f7b5d5/exectiming-2.0.0rc3.tar.gz" } ], "2.0.0rc4": [ { "comment_text": "", "digests": { "md5": "6465d8a9aa7e8f4edbc0c5e4eaeb131b", "sha256": "8946de72b99fe711b9f12698619a94a5a68d433af66308f6c341cb712a8109f7" }, "downloads": -1, "filename": "exectiming-2.0.0rc4-py3-none-any.whl", "has_sig": false, "md5_digest": "6465d8a9aa7e8f4edbc0c5e4eaeb131b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.3", "size": 34140, "upload_time": "2019-06-07T23:58:29", "url": "https://files.pythonhosted.org/packages/4c/6e/33590e7f0401d415e84f5d419e333c3428dcfabab1432f89ae1594f7cc52/exectiming-2.0.0rc4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d834e6d4451c9b86f42ca6b0939390c4", "sha256": "2f30294bdd7b6591338414675266e72cee92ca296c9c3dd324f2f454c031a289" }, "downloads": -1, "filename": "exectiming-2.0.0rc4.tar.gz", "has_sig": false, "md5_digest": "d834e6d4451c9b86f42ca6b0939390c4", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.3", "size": 23028, "upload_time": "2019-06-07T23:58:30", "url": "https://files.pythonhosted.org/packages/24/52/4be142cbd5401b636fa9f14f4064c52dfa4a53eeae10169dc476e507d2b6/exectiming-2.0.0rc4.tar.gz" } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "4066acbc9f9787f557710dd854e9971c", "sha256": "afb0987c5986efad97c493f882b939dd1820b9e931f9e2e3b5b231050b47bc3a" }, "downloads": -1, "filename": "exectiming-2.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "4066acbc9f9787f557710dd854e9971c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.3", "size": 36917, "upload_time": "2019-06-19T00:43:20", "url": "https://files.pythonhosted.org/packages/20/4f/274fada4f50ba937a6f0bb222cf6febd0ffc3c2ff8e9683e823a55805146/exectiming-2.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9e2a0ea6572a844c47d0168352d47242", "sha256": "5027d7d7e0581de4b7a11c8ab50e539e22d6ed646a4119ee3b71aac44fe33590" }, "downloads": -1, "filename": "exectiming-2.0.1.tar.gz", "has_sig": false, "md5_digest": "9e2a0ea6572a844c47d0168352d47242", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.3", "size": 26598, "upload_time": "2019-06-19T00:43:21", "url": "https://files.pythonhosted.org/packages/7a/57/9953097cda02c03d09b19159b75388e55b06b2ea7f8651d8c83f118e255f/exectiming-2.0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "4066acbc9f9787f557710dd854e9971c", "sha256": "afb0987c5986efad97c493f882b939dd1820b9e931f9e2e3b5b231050b47bc3a" }, "downloads": -1, "filename": "exectiming-2.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "4066acbc9f9787f557710dd854e9971c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.3", "size": 36917, "upload_time": "2019-06-19T00:43:20", "url": "https://files.pythonhosted.org/packages/20/4f/274fada4f50ba937a6f0bb222cf6febd0ffc3c2ff8e9683e823a55805146/exectiming-2.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9e2a0ea6572a844c47d0168352d47242", "sha256": "5027d7d7e0581de4b7a11c8ab50e539e22d6ed646a4119ee3b71aac44fe33590" }, "downloads": -1, "filename": "exectiming-2.0.1.tar.gz", "has_sig": false, "md5_digest": "9e2a0ea6572a844c47d0168352d47242", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.3", "size": 26598, "upload_time": "2019-06-19T00:43:21", "url": "https://files.pythonhosted.org/packages/7a/57/9953097cda02c03d09b19159b75388e55b06b2ea7f8651d8c83f118e255f/exectiming-2.0.1.tar.gz" } ] }