{ "info": { "author": "George Usan-Podgornov", "author_email": "", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License" ], "description": "# Qval | Query params validation library\n[![CircleCI](https://circleci.com/gh/OptimalStrategy/Qval/tree/master.svg?style=svg)](https://circleci.com/gh/OptimalStrategy/Qval/tree/master)\n[![Documentation Status](https://readthedocs.org/projects/qval/badge/?version=latest)](https://qval.readthedocs.io/en/latest/?badge=latest)\n[![codecov](https://codecov.io/gh/OptimalStrategy/Qval/branch/master/graph/badge.svg)](https://codecov.io/gh/OptimalStrategy/Qval)\n[![PyPI version](https://badge.fury.io/py/qval.svg)](https://badge.fury.io/py/qval)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) \n\n* [Installation](#installation)\n* [Basic usage](#basic-usage)\n* [Framework-specific instructions](#framework-specific-instructions)\n * [Django Rest Framework](#drf)\n * [Plain Django](#plain-django)\n * [Flask](#flask)\n * [Falcon](#falcon)\n* [Docs](#docs)\n * [Configuration](#configuration)\n * [Logging](#logging)\n\n## Installation\n```bash\n$ pip install qval\n```\n\n## Basic usage\nYou can use Qval both as a function and as a decorator. The function `validate()` accepts 3 positional arguments and 1 named:\n```python\n# qval.py\ndef validate(\n # Request instance. Must be a dictionary or implement request interface.\n request: Union[Request, Dict[str, str]],\n # Dictionary of (param_name -> `Validator()` object).\n validators: Dict[str, Validator] = None,\n # Provide true if you want to access all parameters of the request in the context.\n box_all: bool = True,\n # Factories that will be used to convert parameters to python objects (param -> [callable[str] => object]).\n **factories,\n) -> QueryParamValidator: \n```\nImagine you have a RESTful calculator with an endpoint called `/api/divide`. You can use `validate()` \nto automatically convert parameters to python objects and then validate them:\n```python\nfrom qval import validate\n...\n\ndef division_view(request):\n \"\"\"\n GET /api/divide?\n param a : int\n param b : int, nonzero\n param token : string, length = 12\n \n Example: GET /api/divide?a=10&b=2&token=abcdefghijkl -> 200, {\"answer\": 5}\n \"\"\"\n # Parameter validation occurs in the context manager.\n # If validation fails or user code throws an error, context manager\n # will raise InvalidQueryParamException or APIException respectively.\n # In Django Rest Framework, these exceptions will be processed and result \n # in error codes (400 and 500) on the client side.\n params = (\n # `a` and `b` must be integers\n # Note: in order to get a nice error message on the client side,\n # you factory should raise either ValueError or TypeError\n validate(request, a=int, b=int)\n # `b` must be anything but zero\n .nonzero(\"b\")\n # The `transform` callable will be applied to parameter before the check.\n # In this case we'll get `token`'s length and check if it is equal to 12.\n .eq(\"token\", 12, transform=len)\n )\n # validation starts here\n with params as p:\n return Response({\"answer\": p.a // p.b})\n```\n```json\n// GET /api/divide?a=10&b=2&token=abcdefghijkl\n// Browser:\n{\n \"answer\": 5\n}\n```\nSending b = 0 to this endpoint will result in the following message on the client side:\n```json\n// GET /api/divide?a=10&b=0&token=abcdefghijkl\n{\n \"error\": \"Invalid `b` value: 0.\"\n}\n```\n\n
If you have many parameters and custom validators, it's better to use the `@qval()` decorator:\n```python\n# validators.py\nfrom decimal import Decimal\nfrom qval import Validator, QvalValidationError\n...\n\ndef price_validator(price: int) -> bool:\n \"\"\"\n A predicate to validate `price` query parameter.\n Provides custom error message.\n \"\"\"\n if price <= 0:\n # If price does not match our requirements, we raise QvalValidationError() with a custom message.\n # This exception will be handled in the context manager and will be reraised\n # as InvalidQueryParamException() [HTTP 400].\n raise QvalValidationError(f\"Price must be greater than zero, got \\'{price}\\'.\")\n return True\n\n\npurchase_factories = {\"price\": Decimal, \"item_id\": int, \"token\": None}\npurchase_validators = {\n \"token\": Validator(lambda x: len(x) == 12),\n # Validator(p) can be omitted if there is only one predicate:\n \"item_id\": lambda x: x >= 0,\n \"price\": price_validator,\n}\n\n# views.py\nfrom qval import qval\nfrom validators import *\n...\n\n# Any function or method wrapped with `qval()` must accept request as \n# either first or second argument, and parameters as last.\n@qval(purchase_factories, purchase_validators)\ndef purchase_view(request, params):\n \"\"\"\n GET /api/purchase?\n param item_id : int, positive\n param price : float, greater than zero\n param token : string, len == 12\n\n Example: GET /api/purchase?item_id=1&price=5.8&token=abcdefghijkl\n \"\"\"\n print(f\"{params.item_id} costs {params.price}$.\")\n ...\n```\n\n## Framework-specific instructions\n1. Django Rest Framework works straight out of the box. Simply add `@qval()` to your views or use `validate()` inside.\n\n2. For Django _without_ DRF you may need to add the exception handler to `settings.MIDDLEWARE`. Qval attempts to \ndo it automatically if `DJANO_SETTINGS_MODULE` is set. Otherwise you'll see the following message:\n ```bash\n WARNING:root:Unable to add APIException middleware to the MIDDLEWARE list. Django does not \n support APIException handling without DRF integration. Define DJANGO_SETTINGS_MODULE or \n add 'qval.framework_integration.HandleAPIExceptionDjango' to the MIDDLEWARE list.\n ```\n Take a look at the plain Django example [here](examples/django-example).\n\n3. If you are using Flask, you will need to setup exception handlers:\n ```python\n from flask import Flask\n from qval.framework_integration import setup_flask_error_handlers\n ...\n app = Flask(__name__)\n setup_flask_error_handlers(app)\n ```\n Since `request` in Flask is a global object, you may want to curry `@qval()` before usage:\n ```python\n from flask import request\n from qval import qval_curry\n\n # Firstly, curry `qval()`\n qval = qval_curry(request)\n ...\n \n # Then use it as a decorator.\n # Note: you view now must accept request as first argument\n @app.route(...)\n @qval(...)\n def view(request, params): \n ...\n \n ```\n Check out the full Flask [example](examples/flask-example.py) in `examples/flask-example.py`.
\n \n You can run the example using the command below:\n ```\n $ PYTHONPATH=. FLASK_APP=examples/flask-example.py flask run\n ```\n\n4. Similarly to Flask, with Falcon you will need to setup error handlers:\n ```python\n import falcon\n from qval.framework_integration import setup_falcon_error_handlers\n ...\n app = falcon.API()\n setup_falcon_error_handlers(app)\n ```\n Full Falcon [example](examples/falcon-example.py) can be found here: `examples/falcon-example.py`.
\n \n Use the following command to run the app:\n ```\n $ PYTHONPATH=. python examples/falcon-example.py\n ```\n\n## Docs\nRefer to [documentation](https://qval.rtfd.io) for more verbose descriptions and auto-generated API docs.\nYou can also look at the [tests](tests) to get the idea how the stuff below works.\n\n### Configuration\nQval supports configuration via config files and environmental variables. \nIf `DJANGO_SETTINGS_MODULE` or `SETTINGS_MODULE` are defined, the specified config module will be used. Otherwise, \nall lookups would be done in `os.environ`.

\nSupported variables:\n* `QVAL_MAKE_REQUEST_WRAPPER = myapp.myfile.my_func`. Customizes behaviour of the `make_request()` function, \nwhich is applied to all incoming requests, then the result is passed to `qval.qval.QueryParamValidator`. \nThe provided function must accept `request` and return object that supports request interface \n(see `qval.framework_integration.DummyReqiest`).\n
For example, the following code adds logging to each `make_request()` call:\n\n ```python\n # app/utils.py\n def my_wrapper(f):\n @functools.wraps(f)\n def wrapper(request):\n print(f\"Received new request: {request}\")\n return f(request)\n return wrapper\n ```\n You also need to execute `export QVAL_MAKE_REQUEST_WRAPPER=app.utils.my_wrapper` in your console\n or to add it to the config file.\n* `QVAL_REQUEST_CLASS = path.to.CustomRequestClass`. `@qval()` will use it to determine which argument is a request. \nIf you have a custom request class that implements `qval.framework_integration.DummyRequest` interface, provide it with this variable.\n\n* `QVAL_LOGGERS = [mylogger.factory, ...] | mylogger.factory`. List of paths or a path to a factory callable. \nSpecified callable must return object with the `Logger` interface. See section [logging](#logging) for more info.\n\n### Logging\nQval uses a global object called `log` acting as singleton when reporting errors. By default, `logging.getLogger` \nfunction is used as a factory on each call. You can provide your own factory (see [configuration](#configuration)) or disable logging. Example error message:\n```bash\nAn error occurred during the validation or inside of the context: exc `` ((34, 'Numerical result out of range')).\n| Parameters: \n| Body : b''\n| Exception:\nTraceback (most recent call last):\n File \"/qval/qval.py\", line 338, in inner\n return f(*args, params, **kwargs)\n File \"/examples/django-example/app/views.py\", line 46, in pow_view\n return JsonResponse({\"answer\": params.a ** params.b})\nOverflowError: (34, 'Numerical result out of range')\nInternal Server Error: /api/pow\n[19/Nov/2018 07:03:15] \"GET /api/pow?a=2.2324&b=30000000 HTTP/1.1\" 500 102\n```\n\nImport the `log` object from `qval` and configure as you need:\n```python\nfrom qval import log\n# For instance, disable logging:\nlog.disable()\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/OptimalStrategy/Qval", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "qval", "package_url": "https://pypi.org/project/qval/", "platform": "", "project_url": "https://pypi.org/project/qval/", "project_urls": { "Homepage": "https://github.com/OptimalStrategy/Qval" }, "release_url": "https://pypi.org/project/qval/0.3.3/", "requires_dist": null, "requires_python": "", "summary": "This module provides a convenient API for verifying query parameters.", "version": "0.3.3" }, "last_serial": 5403019, "releases": { "0.1.5": [ { "comment_text": "", "digests": { "md5": "73c3e98a3e5d2dd00d450ba809aa9b45", "sha256": "e072537e0bd7ee5f27fdbe93b9c680bb4759c9b4a5e3d63c22490f816714c3b1" }, "downloads": -1, "filename": "qval-0.1.5-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "73c3e98a3e5d2dd00d450ba809aa9b45", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 32783, "upload_time": "2018-11-17T02:21:39", "url": "https://files.pythonhosted.org/packages/1a/69/4f1c7f88d069dd23924dc89207275c5d3dd0c27f703dddf89e81c379881b/qval-0.1.5-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f1f947e5f27b85b0bcc03d700b908014", "sha256": "e7b10ccd7cca244bee678efe88c888db59177f3200ea738d358c4039ab08bf38" }, "downloads": -1, "filename": "qval-0.1.5.tar.gz", "has_sig": false, "md5_digest": "f1f947e5f27b85b0bcc03d700b908014", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16610, "upload_time": "2018-11-17T02:22:20", "url": "https://files.pythonhosted.org/packages/ce/5a/0335ea4cddaf7a1358f41d2e729183449e3e31e46525c8c6417c1be64559/qval-0.1.5.tar.gz" } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "fc01454a851aedc2ea1abc39f2d66cef", "sha256": "9fe70b543ac5fa5f14ab0aaa32b00666c3f5b4adfdff50594f804bd4c214b6f4" }, "downloads": -1, "filename": "qval-0.1.6-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "fc01454a851aedc2ea1abc39f2d66cef", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 33974, "upload_time": "2018-11-18T00:41:40", "url": "https://files.pythonhosted.org/packages/5e/f5/f0c9f0a5e66328a1aa710219e326897ec7acb00a6fd2c0b185d3efecc0d7/qval-0.1.6-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "57e04b3e7b4a7053d346d1c1120ec6a6", "sha256": "66b3ca64af978fffbd079328f4167b9e26937ee645a61760fc92e0845ef44e0f" }, "downloads": -1, "filename": "qval-0.1.6.tar.gz", "has_sig": false, "md5_digest": "57e04b3e7b4a7053d346d1c1120ec6a6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17544, "upload_time": "2018-11-18T00:41:45", "url": "https://files.pythonhosted.org/packages/d3/11/6b3499370c27479095c89fb9ea838d6edfcb07e7040b23ab7200d951e2f7/qval-0.1.6.tar.gz" } ], "0.1.7": [ { "comment_text": "", "digests": { "md5": "85faf6db6017cf0f66c295f4c0b56431", "sha256": "2ae34e5b83776ce638d495774b16e91d622c8c2148e624261a9d2f345e53eae1" }, "downloads": -1, "filename": "qval-0.1.7-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "85faf6db6017cf0f66c295f4c0b56431", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 37304, "upload_time": "2018-11-19T16:19:51", "url": "https://files.pythonhosted.org/packages/4d/fd/727ea01f8f52fa88edd3981efacf49527631d9c5544c3e2b0d27e6fd01d8/qval-0.1.7-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f0693f16fbdaa8fe6c4bb5bfb55266d7", "sha256": "88ed8ceaaaabc70a93f703e3fcde3e03b966facbef4a481857943dddbe814664" }, "downloads": -1, "filename": "qval-0.1.7.tar.gz", "has_sig": false, "md5_digest": "f0693f16fbdaa8fe6c4bb5bfb55266d7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20897, "upload_time": "2018-11-19T16:19:57", "url": "https://files.pythonhosted.org/packages/e7/be/02d4246638c9324ed1c25cd8c9750e99b6a19170242d0ab7cae926938df8/qval-0.1.7.tar.gz" } ], "0.1.8": [ { "comment_text": "", "digests": { "md5": "5029d5f7d91a7759a155a6ce673caa59", "sha256": "6b6e6d3b92ad550a71d35d4819735379cedcd1c9e656dd0b2788f260c0b64e3e" }, "downloads": -1, "filename": "qval-0.1.8-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "5029d5f7d91a7759a155a6ce673caa59", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 38948, "upload_time": "2018-12-11T17:04:14", "url": "https://files.pythonhosted.org/packages/3b/50/1087df401a2222d2ae26ad5fd95506345adb1432a079f7ab60ee8e307c86/qval-0.1.8-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6375e1112546658491e17537ad41002e", "sha256": "ddaf4b939ee3f37b927f3de6fb40955f10868304d940e9bededefbf4685620ff" }, "downloads": -1, "filename": "qval-0.1.8.tar.gz", "has_sig": false, "md5_digest": "6375e1112546658491e17537ad41002e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22510, "upload_time": "2018-12-11T17:04:20", "url": "https://files.pythonhosted.org/packages/fc/d1/226b2cb3e5d32b8edcb538cc042bf335433f79802b417ab95c85439f3d97/qval-0.1.8.tar.gz" } ], "0.1.9": [ { "comment_text": "", "digests": { "md5": "795b9865df13b8fdf5e09c7d763617b5", "sha256": "565a60a7ae39f7b32f820daaecd164d56c70e7c8c9550a050180e670769b8816" }, "downloads": -1, "filename": "qval-0.1.9-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "795b9865df13b8fdf5e09c7d763617b5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 40898, "upload_time": "2018-12-13T05:32:53", "url": "https://files.pythonhosted.org/packages/c5/ed/4430e89f6da0d7774004839756f4f82987b5d5743daaace59eead6150da6/qval-0.1.9-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c453f50632a04a5cfe76b780c07e0074", "sha256": "0736d01a9c2a4ba8c943d8e668c729961f66adb494861e9527c101e948bd62ac" }, "downloads": -1, "filename": "qval-0.1.9.tar.gz", "has_sig": false, "md5_digest": "c453f50632a04a5cfe76b780c07e0074", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29407, "upload_time": "2018-12-13T05:32:58", "url": "https://files.pythonhosted.org/packages/ea/31/e0794ee04a3e46bccb1cd904518cdef714156d3c33f52de9e9f6a5096a70/qval-0.1.9.tar.gz" } ], "0.1.99": [ { "comment_text": "", "digests": { "md5": "cb7215742e5364851bba4fcd4201cd4c", "sha256": "9f301d87219d35c860935f57dae3169d53179cac2a45a11f7fbd0e86fcde5e6a" }, "downloads": -1, "filename": "qval-0.1.99-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "cb7215742e5364851bba4fcd4201cd4c", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 41306, "upload_time": "2018-12-14T22:05:33", "url": "https://files.pythonhosted.org/packages/83/54/7e7745f89678a55cb8aa664328970d14dc035a2e43bf11537ac174abf013/qval-0.1.99-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ab68d0a530df17059bfcfbf59faa75fd", "sha256": "2b68cbe123017da7a7465302051c2eaf77ed35cb708d067ee16d35670cd44624" }, "downloads": -1, "filename": "qval-0.1.99.tar.gz", "has_sig": false, "md5_digest": "ab68d0a530df17059bfcfbf59faa75fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30212, "upload_time": "2018-12-14T22:05:40", "url": "https://files.pythonhosted.org/packages/d5/66/9f44f3fcfd0cb2afeab9938717423b1f68fdb464e459e451417533f5eb57/qval-0.1.99.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "094d0945b472dbb54edde6c1b98ed78d", "sha256": "d8d7731d0429519ecc85e1f3a587c5a1e76b62f9f0af49856cdc8dbeb40486ef" }, "downloads": -1, "filename": "qval-0.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "094d0945b472dbb54edde6c1b98ed78d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 41336, "upload_time": "2018-12-19T19:53:52", "url": "https://files.pythonhosted.org/packages/76/7c/6f2962642f785d1f0d30dcc0edbeef6075d0b9f12905c2984f019b16c4e3/qval-0.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "38bbc16e71949da7bf6b1aa740da27bd", "sha256": "053b6f0f6813e450dbf2a30440232bca9382dc255c4e0b8f9721ab8d0c750a99" }, "downloads": -1, "filename": "qval-0.2.0.tar.gz", "has_sig": false, "md5_digest": "38bbc16e71949da7bf6b1aa740da27bd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30081, "upload_time": "2018-12-19T19:53:58", "url": "https://files.pythonhosted.org/packages/f4/54/fbb107303089e42604e21f92705bf029e9198f9df7cd171a2d2643ac0a21/qval-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "7f99dc4af6c1146a5d7ce82029e5fe36", "sha256": "ce694702c83260399c0d450988e494cd56ba7f0c0da4e4191216904fc4f8bcf7" }, "downloads": -1, "filename": "qval-0.2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7f99dc4af6c1146a5d7ce82029e5fe36", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 44094, "upload_time": "2019-01-16T02:29:50", "url": "https://files.pythonhosted.org/packages/c9/4b/fb8659189df3d934f1bcb77733217b33abf9629c27478162e4037f35b73a/qval-0.2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "debbdf58c57cdfd187d0e09ddd10c722", "sha256": "60f95a62d2a81b35a44352dc2f33ee82eae6b32b7947d5ce0feeaec952cd433e" }, "downloads": -1, "filename": "qval-0.2.1.tar.gz", "has_sig": false, "md5_digest": "debbdf58c57cdfd187d0e09ddd10c722", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31582, "upload_time": "2019-01-16T02:29:57", "url": "https://files.pythonhosted.org/packages/20/47/9c489666a50c3c62296c8446c9d662f76af1bf3fa4d16aaa09e1e0375a0d/qval-0.2.1.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "fb52cf2cd3b5907af531d5c0b8632c84", "sha256": "a6c1c4592f35a701fb0604e587ba86b18a716c813d630da38894d7ebd5d8219f" }, "downloads": -1, "filename": "qval-0.3.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "fb52cf2cd3b5907af531d5c0b8632c84", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 44894, "upload_time": "2019-05-26T23:58:51", "url": "https://files.pythonhosted.org/packages/10/dc/3aba4fef8be3388006808dd8ddf40188d2f34db490b82a3cc2175112430d/qval-0.3.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "cf5fb265d8c173e816a69c6730fcea6f", "sha256": "1a7d261104319345d0bcec6a3f7438b520a2d0a9eb0a68322effd0459e61a68a" }, "downloads": -1, "filename": "qval-0.3.1.tar.gz", "has_sig": false, "md5_digest": "cf5fb265d8c173e816a69c6730fcea6f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32027, "upload_time": "2019-05-26T23:58:57", "url": "https://files.pythonhosted.org/packages/05/70/df3ce86dc48e86d556459faa1e2c4291b94a39a838334907e1e0794bcdbf/qval-0.3.1.tar.gz" } ], "0.3.3": [ { "comment_text": "", "digests": { "md5": "7a06bedbb439d1673fff695f12e493ed", "sha256": "0dc3855e52496ec45fe0560d4ee2e268f963f3b03d8671b9402d136e57b8f610" }, "downloads": -1, "filename": "qval-0.3.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7a06bedbb439d1673fff695f12e493ed", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 44902, "upload_time": "2019-06-15T04:50:29", "url": "https://files.pythonhosted.org/packages/cf/30/603150e1d2221557560ec32bee771e6efbf7d6a82d5ae6152c6746da3652/qval-0.3.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6205c5c4a7f36d0883b34d2d65258716", "sha256": "27f34bde9cf19995a9688f483be44d24048242d69f411f1e0af0f20bb22abe20" }, "downloads": -1, "filename": "qval-0.3.3.tar.gz", "has_sig": false, "md5_digest": "6205c5c4a7f36d0883b34d2d65258716", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32106, "upload_time": "2019-06-15T04:50:33", "url": "https://files.pythonhosted.org/packages/18/eb/5518b2c49e7850eaae1106539352bbc7d7951eacee36153f86c6396ac4d3/qval-0.3.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7a06bedbb439d1673fff695f12e493ed", "sha256": "0dc3855e52496ec45fe0560d4ee2e268f963f3b03d8671b9402d136e57b8f610" }, "downloads": -1, "filename": "qval-0.3.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7a06bedbb439d1673fff695f12e493ed", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 44902, "upload_time": "2019-06-15T04:50:29", "url": "https://files.pythonhosted.org/packages/cf/30/603150e1d2221557560ec32bee771e6efbf7d6a82d5ae6152c6746da3652/qval-0.3.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6205c5c4a7f36d0883b34d2d65258716", "sha256": "27f34bde9cf19995a9688f483be44d24048242d69f411f1e0af0f20bb22abe20" }, "downloads": -1, "filename": "qval-0.3.3.tar.gz", "has_sig": false, "md5_digest": "6205c5c4a7f36d0883b34d2d65258716", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32106, "upload_time": "2019-06-15T04:50:33", "url": "https://files.pythonhosted.org/packages/18/eb/5518b2c49e7850eaae1106539352bbc7d7951eacee36153f86c6396ac4d3/qval-0.3.3.tar.gz" } ] }