{
"info": {
"author": "George Usan-Podgornov",
"author_email": "",
"bugtrack_url": null,
"classifiers": [
"License :: OSI Approved :: MIT License"
],
"description": "# Qval | Query params validation library\n[](https://circleci.com/gh/OptimalStrategy/Qval/tree/master)\n[](https://qval.readthedocs.io/en/latest/?badge=latest)\n[](https://codecov.io/gh/OptimalStrategy/Qval)\n[](https://badge.fury.io/py/qval)\n[](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 `