# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['chocs', 'chocs.json_schema', 'chocs.middleware', 'chocs.serverless']

package_data = \
{'': ['*']}

install_requires = \
['pyyaml>=5.3.1,<6.0.0', 'typing_extensions>=3.7.4,<4.0.0']

setup_kwargs = {
    'name': 'chocs',
    'version': '0.9.6',
    'description': 'Lightweight and powerful wsgi rest framework for rapid building applications based on wsgi servers.',
    'long_description': '# Chocs [![PyPI version](https://badge.fury.io/py/chocs.svg)](https://pypi.org/project/chocs/) ![Release](https://github.com/kodemore/chocs/workflows/Release/badge.svg) ![Linting and Tests](https://github.com/kodemore/chocs/workflows/Linting%20and%20Tests/badge.svg) [![codecov](https://codecov.io/gh/kodemore/chocs/branch/master/graph/badge.svg)](https://codecov.io/gh/kodemore/chocs) [![Maintainability](https://api.codeclimate.com/v1/badges/9e3c979283b2361a9174/maintainability)](https://codeclimate.com/github/kodemore/chocs/maintainability)\nChocs is a modern HTTP framework for building AWS HTTP API/REST API and WSGI compatible applications. \nChocs aims to be small, expressive, and robust. \nIt provides an elegant API for writing fault-proof, extensible microservices.  \n\n## Features\n\n - AWS Serverless integration\n - Open api integration  \n - Elegant and easy API\n - No additional bloat like built-in template engines, session handlers, etc.\n - Compatible with all WSGI servers\n - Loosely coupled components which can be used separately\n - Multipart body parsing\n - Graceful error handling\n - HTTP middleware support\n - Fast routing\n\n## Installation\n```\npip install chocs\n```\n\n# Usage\n\n## Quick start\n\n```python\nfrom chocs import Application\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\nfrom chocs import serve\n\n\nhttp = Application()\n\n@http.get("/hello/{name}")\ndef hello(request: HttpRequest) -> HttpResponse:\n    return HttpResponse(f"Hello {request.path_parameters.get(\'name\')}!")\n\nserve(http)\n```\n\n > Keep in mind that the `serve()` function is using the `bjoern` package, so make sure you included it in your project \n > dependencies before using it. You are able to use any WSGI compatible server.\n\n## Running application with Gunicorn (or any other WSGI server)\n\n```python\n# myapp.py\nfrom chocs import Application\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\nfrom chocs import create_wsgi_handler\n\n\nhttp = Application()\n\n\n@http.get("/hello/{name}*")\ndef hello(request: HttpRequest) -> HttpResponse:\n    return HttpResponse(f"Hello {request.path_parameters.get(\'name\')}!")\n\napp = create_wsgi_handler(http, debug=False)\n```\n\n```bash\ngunicorn -w 4 myapp:app\n```\n\n## Running application in AWS Lambda (Http api or rest api)\n\n`handler.py`\n```python\nimport logging\n\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\nfrom chocs import Application\n\nlogger = logging.getLogger()\nlogger.setLevel(logging.INFO)\n\n\nhttp = Application()\n\n\n@http.get("/hello/{name}")\ndef hello_handler(request: HttpRequest) -> HttpResponse:\n    logger.info("Hello AWS!")\n    logger.info(request.attributes.get("aws_context"))\n    logger.info(request.attributes.get("aws_event"))\n    return HttpResponse(f"Hello {request.path_parameters.get(\'name\')}")\n\n\n__all__ = ["hello_handler"]\n```\n\n`serverless.yml`\n```yaml\nservice: aws-hello-name\n\nprovider:\n  name: aws\n  runtime: python3.8\n\nplugins:\n  - serverless-python-requirements\n\ncustom:\n  pythonRequirements:\n    dockerizePip: true\n\nfunctions:\n  hello_name:\n    handler: handler.hello_handler\n    events:\n      - httpApi:\n          method: GET\n          path: /hello/{name}\n```\n\n```bash\nserverless deploy\n```\n\n## Routing\nChocs is shipped with a built-in routing module. The easiest way to utilise chocs\' routing is to use `chocs.router` object.\n`chocs.router` is an instance of the module\'s internal class `chocs.application.Application`, which provides a simple API \nwhere each function is a decorator corresponding to an HTTP method.\n\n```python\nfrom chocs import Application, HttpResponse, HttpRequest\n\n\nhttp = Application()\n\n\n@http.get("/hello")\ndef hello(req: HttpRequest) -> HttpResponse:\n    ...\n```\n\nThe above example will assign the hello function to handle a `GET /hello` request. \n\nAvailable methods:\n- `delete`\n- `get`\n- `head`\n- `options`\n- `patch`\n- `post`\n- `put`\n- `trace`\n\n### Parametrized routes\n\nRoutes can contain parameterised parts. Parameters must be enclosed within `{` and `}`.\n\n```python\nfrom chocs import Application\n\nhttp = Application()\n\n\n@http.get("/pet/{id}")\ndef hello():\n    ...\n```\nWill match the following URIs:\n - `/pet/1`\n - `/pet/abc`\n - `/pet/abc1`\n \n### Wildcard routes\n\nAsterisks (`*`) can be used in the route\'s pattern to match any possible combination. Keep in mind that routes which \n_do not_ contain wildcards are prioritised over routes with wildcards.\n\n```python\nfrom chocs import Application\n\nhttp = Application()\n\n\n@http.get("/pet/*", id)\ndef hello():\n    ...\n```\n\nThe above example will match following URIs:\n- `/pet/a`\n- `/pet/a/b/c`\n- `/pet/12jd/fds`\n\n### Route groups\n\nChocs supports route groups. Route groups is implemented through [context lib interface](https://docs.python.org/3/library/contextlib.html).\nIf you need to split your application in smaller chunks with standalone req/res handlers consider the\nfollowing example:\n\n```python\nfrom threading import Thread\n\nfrom chocs.wsgi import serve \nfrom chocs import Application\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\n\nmain_app = Application()\n\nwith main_app.group("/users/{id}") as user_module:\n    \n    @user_module.post("/profile_picture")  # POST /users/{id}/profile_pictures\n    def create_profile_picture(request: HttpRequest) -> HttpResponse:\n        ...\n    \n    @user_module.get("/profile_picture")  # GET /users/{id}/profile_pictures\n    def get_profile_picture(request: HttpRequest) -> HttpResponse:\n        ...\n    \n    @user_module.get("/badges") # GET /users/{id}/badges\n    def badges(request: HttpRequest) -> HttpResponse:\n        ...\n\nwith main_app.group("/payments") as payment_module:\n\n    @payment_module.get("/analytics") # GET /payments/analytics\n    def get_analytics(request: HttpRequest) -> HttpResponse:\n        ...\n\nif __name__ == \'__main__\':\n    def wsgi_user_module():\n        serve(user_module, port=8081)\n    def wsgi_payment_module():\n        serve(payment_module, port=8082)\n\n    Thread(target=wsgi_user_module).start()\n    payment_module()\n```\n\nThe above example shows how to run two different modules, which support their own routes\non two different ports in the one process.\n\n## Middleware\n\nMiddleware are functions or classes that inherit `chocs.Middleware`. Middlewares have access to the request object\nand the `next` function which can be used to control middleware stack flow. Successful middleware execution should call\nthe `next` function which accepts a `chocs.HttpRequest` instance and returns `chocs.HttpReponse`.\n\nMiddlewares can perform various tasks:\n- Making changes in request/response objects ending\n- Validating input data\n- Authenticating users\n- End request-response cycle\n- Connecting to external data sources\n\n## Integration with openapi\n\nChocs provides middleware which can be used to validate input data and simplify working with \ninputs by mapping them to dataclasses. To provide automatic validation for your request based\non open api specification, use `chocs.middleware.OpenApiMiddleware`:\n\n```python\nfrom chocs.middleware import OpenApiMiddleware\nfrom chocs import Application, HttpRequest, HttpResponse\nfrom os import path\nfrom dataclasses import dataclass\n\n# absolute path to file containing open api documentation; yaml and json files are supported\nopenapi_filename = path.join(path.dirname(__file__), "/openapi.yml")\n\n# instantiating application and passing open api middleware\napp = Application(OpenApiMiddleware(openapi_filename, validate_body=True, validate_query=True))\n\n# defining our dataclass for better type support\n@dataclass()\nclass Pet:\n    id: str\n    name: str\n\n# the registered route must correspond to open api route within `path` section.\n# if request body is invalid the registered function will not be executed\n@app.post("/pets", parsed_body=Pet, strict=True) # `parsed_body` parameter can be used to map request to certain type\ndef create_pet(request: HttpRequest) -> HttpResponse:\n    try: # mapping request to your dataclass may fail\n        pet: Pet = request.parsed_body\n    except TypeError:\n        return HttpResponse(status=400)\n    \n    # if request body is valid `request.parsed_body` will be instance of a Pet\n    assert isinstance(pet, Pet)\n    return HttpResponse(pet.name)\n```\n\n> Open api file used in the example above can be [found here](./examples/input_validation_with_open_api/openapi.yml)\n\nChocs automatically validates:\n - request body, `application/json` header must be present for successful validation\n - query string parameters\n - request headers\n\n### Turning strict mode off\n\n> Note: By default chocs works in a strict mode, which means when you map request\n> data to your object `__init__` method is called. To override this behaviour set\n> `strict` property to false: `@app.post("/pets", parsed_body=Pet, strict=False)`\n\nIn non strict mode your request body will be still mapped to the provided class, and data will be hydrated but `__init__` method will not be called.\nPlease consider the following example:\n\n```python\nfrom chocs.middleware import OpenApiMiddleware\nfrom chocs import Application, HttpRequest, HttpResponse\nfrom os import path\nfrom dataclasses import dataclass\n\nopenapi_filename = path.join(path.dirname(__file__), "/openapi.yml")\napp = Application(OpenApiMiddleware(openapi_filename, validate_body=True, validate_query=True))\n\n@dataclass()\nclass Pet:\n    id: str\n    name: str\n\n@app.post("/pets", parsed_body=Pet, strict=False) # with strict mode off, __init__ method of Pet\'s class is ignored.\ndef create_pet(request: HttpRequest) -> HttpResponse:\n    pet: Pet = request.parsed_body # this will always work\n\n    assert isinstance(pet, Pet)\n    return HttpResponse(pet.name)\n```\n\n\n### Handling validation errors with custom middleware\n\nBy default, if validation fails users will see `500 response`. This behavior can be changed if custom middleware that\ncatches validation errors is defined and used in application.\n\n### Defining and using a custom middleware\n \nThe following code defines simple function middleware to catch validation errors when they appear and notifies users:\n\n```python\nfrom chocs.middleware import OpenApiMiddleware\nfrom chocs.json_schema.errors import ValidationError\nfrom chocs import Application, HttpRequest, HttpResponse\nfrom dataclasses import dataclass\nimport json\nfrom typing import Callable\nfrom os import path\n\nopenapi_filename = path.join(path.dirname(__file__), "/openapi.yml")\n\n\n# middleware must always accept two parameters; HttpRequest and Callable and return HttpResponse\ndef handle_errors(request: HttpRequest, next: Callable) -> HttpResponse:\n    try:\n        return next(request) # we pass request further to middleware pipeline\n    except ValidationError as error: # if exception is thrown it is caught here and new response is generated instead\n        json_response = {\n            "code": error.code,\n            "message": str(error),\n        }\n        return HttpResponse(json.dumps(json_response), status=422)\n    \n# error handling middleware must go before open api one to catch errors thrown inside open api middleware\napp = Application(handle_errors, OpenApiMiddleware(openapi_filename))\n\n@dataclass()\nclass Pet:\n  id: str\n  name: str\n\n@app.post("/pets", parsed_body=Pet)\ndef create_pet(request: HttpRequest) -> HttpResponse:\n  assert isinstance(request.parsed_body, Pet)\n\n  pet = request.parsed_body\n  return HttpResponse(pet.name)\n```\n\nFull working example can be found inside [examples directory](./examples/input_validation_with_open_api)\n\n## Request\n`chocs.Request` object is an abstraction around WSGI\'s environment and `wsgi.input` data with handy interface \nto ease everyday work.\n\n#### `chocs.Request.headers:chocs.HttpHeaders (read-only)`\nKeeps parsed headers in dict-like object.\n\n#### `chocs.Request.body:io.BytesIO` \nRaw body data\n\n#### `chocs.Request.parsed_body:chocs.HttpMessage`\nDepending on the content type it could be one of the following:\n - `chocs.FormHttpMessage`\n - `chocs.JsonHttpMessage`\n - `chocs.MultipartHttpMessage`\n - `chocs.YamlHttpMessage`\n\n#### `chocs.Request.as_dict(): dict`\nTries to convert request body to a dict and returns it.\n\n> Note this will only work with json and yaml content types.\n\n#### `chocs.Request.as_str(): str`\nReturns request content as a string.\n \n#### `chocs.Request.cookies:typing.List[chocs.HttpCookie]` \nRequest\'s cookies\n\n#### `chocs.Request.method:chocs.HttpMethod`\nThe request\'s method\n\n#### `chocs.Request.path:str`\nThe request\'s path\n\n#### `chocs.Request.query_string:chocs.HttpQueryString`\nA dict like object with parsed query string with JSON forms support\n        \n#### `chocs.Request.path_parameters:dict`\nMatched route parameters, for example when `/users/john` matches the `/users/{name}` route, parameters will contain a \n`name` key with a value of `john`\n\n#### `chocs.Request.attributes:dict`\nOther environmental or custom attributes attached to the request object, eg.: `aws_event` or `aws_context`\nwhen running chocs app as aws lambda.\n\n## Response\n`chocs.Response` object is a part of request-response flow and it is required to be returned by all functions\ndecorated with `router.*` method. Instance of the response class is recognised by `chocs.Application` and used to \ngenerate real response served to your clients.\n\n#### `chocs.Response.body: io.BytesIO` \nBody served to server\'s clients.\n\n### `chocs.Response.status_code: chocs.HttpStatus`\nValid response code, instance of `chocs.HttpStatus` enum can be used or just a status code\'s number.\n\n#### `chocs.Response.cookies:chocs.HttpCookieJar` \nResponse\'s cookies\n\n#### `chocs.Response.write(body: Union[bytes, str, bytearray])`\nWrite bytes to response body\n\n#### `chocs.Response.close()`\nMakes body non-writable.\n\n#### `chocs.Response.writable: bool`\nIndicates whether response\'s body is writable.\n\n#### `chocs.Response.parsed_body:chocs.HttpMessage`\nDepending on the content type it could be one of the following:\n- `chocs.FormHttpMessage`\n- `chocs.JsonHttpMessage`\n- `chocs.MultipartHttpMessage`\n- `chocs.YamlHttpMessage`\n\n#### `chocs.Response.as_dict(): dict`\nTries to convert response body to a dict and returns it.\n\n> Note this will only work with json and yaml content types.\n\n#### `chocs.Response.as_str(): str`\nReturns response content as a string.\n\n## Working with cookies\n\n`chocs.HttpCookieJar` object takes care of cookie handling. It can be accessed in dict-like manner, when item is requested,\ninstance of `chocs.HttpCookie` is returned to user. \n\nCookies can be set either by passing string value to the `chocs.CookieJar`\'s key, or by calling `chocs.CookieJar.append` \nmethod which accepts instance of `chocs.Cookie`.\n\n### Reading client cookies\n\nCookies can be easily accessed from `chocs.Request.cookies` object which is injected as a parameter to each function \nregistered as route handler. Consider the following example:\n\n```python\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\nfrom chocs import Application\nfrom chocs import serve\n\nhttp = Application()\n\n\n@http.get("/cookies")\ndef read_cookies(request: HttpRequest) -> HttpResponse:\n\n    message = "Hello"\n    if "user_name" in request.cookies:\n        message += f", {str(request.cookies[\'user_name\'])}"\n    message += "!"\n\n    return HttpResponse(body=message)\n\nserve(http)\n```\n\n### Setting cookies\n```python\nfrom datetime import datetime\n\nfrom chocs import HttpCookie\nfrom chocs import HttpRequest\nfrom chocs import HttpResponse\nfrom chocs import Application\nfrom chocs import serve\n\nhttp = Application()\n\n\n@http.get("/cookies")\ndef read_cookies(request: HttpRequest) -> HttpResponse:\n    response = HttpResponse(body="Hi! I have baked some cookies for ya!")\n    response.cookies[\'simple-cookie\'] = "Simple cookie for simple people"\n    response.cookies.append(HttpCookie("advanced-cookie", "This cookie will expire in 2021-01-01", expires=datetime(2021, 1, 1)))\n    return response\n\nserve(http)\n```\n\n# Contributing\n\n## Prerequisites\n\n- libev\n- python 3.8\n- docker\n\n## Installation\n\n`poetry install`\n\n## Running tests\n\n`poetry run pytest`\n\n## Linting\n\n```shell\npoetry run black .\npoetry run isort .\npoetry run mypy .\n```\n\n## PR \n',
    'author': 'Dawid Kraczkowski',
    'author_email': 'dawid.kraczkowski@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/kodemore/chocs',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
