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

packages = \
['asymmetric', 'asymmetric.callbacks', 'asymmetric.cli']

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

install_requires = \
['httpx>=0.16.1,<0.17.0', 'starlette>=0.13.8,<0.14.0']

entry_points = \
{'console_scripts': ['asymmetric = asymmetric.cli.core:dispatcher']}

setup_kwargs = {
    'name': 'asymmetric',
    'version': '0.1.6',
    'description': 'The async framework that calls you back! Enable ridiculously fast and easy module-to-API transformations. Learn in minutes, implement in seconds.',
    'long_description': '# Asymmetric\n\n![PyPI - Version](https://img.shields.io/pypi/v/asymmetric?style=for-the-badge&logo=python&color=306998&logoColor=%23fff&label=version)\n\n_The async framework that calls you back_! ✨ Enable ridiculously fast and easy module-to-**[API](https://en.wikipedia.org/wiki/Web_API)** transformations. Learn in minutes, implement in seconds.\n\n![Tests Coverage](https://img.shields.io/codecov/c/gh/daleal/asymmetric?color=%2344cc11&label=coverage&logo=codecov&logoColor=ffffff&style=for-the-badge)\n![Linters](https://img.shields.io/github/workflow/status/daleal/asymmetric/linters?label=linters&logo=github&style=for-the-badge)\n\n## Why Asymmetric?\n\nRaw developing speed and ease of use, that\'s why. `asymmetric` is based on **[Starlette](https://github.com/encode/starlette)** ✨! While `Starlette` is a powerful tool to have, getting it to work from scratch can be a bit of a pain, especially if you have never used it before. The idea behind `asymmetric` is to be able to take any module **already written** and transform it into a working API in a matter of minutes, instead of having to design the module ground-up to work with `Starlette` (it can also be used to build an API from scratch really fast). With `asymmetric`, you will also get some neat features, namely:\n\n- Auto logging.\n- Server-side error detection and exception handling.\n- **Asynchronous callback endpoints** to make a request, terminate the request **immediately** and then have the server make a request to a _callback_ endpoint with the results! ✨\n- ~~Auto-generated `/docs` endpoint for your API with **interactive documentation**.~~ **[UNDER CONSTRUCTION]**\n- ~~Auto-generated [OpenAPI Specification](https://swagger.io/docs/specification/about/) documentation files for your API.~~ **[UNDER CONSTRUCTION]**\n\nThe [complete documentation](https://asymmetric.one/docs/) is available on the [official website](https://asymmetric.one/).\n\n## Installing\n\nInstall using pip!\n\n```sh\npip install asymmetric\n```\n\n## Usage\n\n### Running the development server\n\nTo start a server, choose your favorite `ASGI` server and target the `asymmetric` object!\n\n```sh\nuvicorn <module>:asymmetric\n```\n\nWhere `<module>` is your module name (in the examples, we will be writing in a file named `module.py`, so the module name will be just `module`). A `Starlette` instance will be spawned immediately and can be reached at [http://127.0.0.1:8000](http://127.0.0.1:8000) by default. We don\'t have any endpoints yet, so we\'ll add some later.\n\n### Defining the API endpoints\n\nThe module consists of a main object called `asymmetric`, which includes an important element: the `router` decorator. Let\'s analyze it:\n\n```py\nfrom asymmetric import asymmetric\n\n@asymmetric.router("/some-route", methods=["post"], response_code=200, callback=False)\n```\n\nThe decorator recieves 4 arguments: the `route` argument (the endpoint of the API to which the decorated function will map), the `methods` argument (a list of the methods accepted to connect to that endpoint, defaults in only `POST` requests), the `response_code` argument (the response code of the endpoint if everything goes according to the plan. Defaults to `200`) and the `callback` argument (a boolean or an object specifying the request style for that endpoint, defaults to `False`, generating normal endpoint behaviour). The `callback` attribute will have its own section below, for now we will use the default `callback=False`.\n\nNow let\'s imagine that we have the following method:\n\n```py\ndef some_function():\n    """Greets the world."""\n    return "Hello World!"\n```\n\nTo transform that method into an API endpoint, all you need to do is add one line:\n\n```py\n@asymmetric.router("/sample", methods=["get"])\ndef some_function():\n    """Greets the world."""\n    return "Hello World!"\n```\n\nRun `uvicorn module:asymmetric` and send a `GET` request to `http://127.0.0.1:8000/sample`. You should get a `Hello World!` in response! (To try it with a browser, make sure to run the above command and click [this link](http://127.0.0.1:8000/sample)).\n\nBut what about methods with arguments? Of course they can be API\'d too! Let\'s now say that you have the following function:\n\n```py\ndef another_function(a, b=372):\n    """\n    Adds :a and :b and returns the result of\n    that operation.\n    """\n    return a + b\n```\n\nTo transform that method into an API endpoint, all you need to do, again, is add one line:\n\n```py\n@asymmetric.router("/add")\ndef another_function(a, b=372):\n    """\n    Adds :a and :b and returns the result of\n    that operation.\n    """\n    return a + b\n```\n\n### Querying API endpoints\n\nTo give parameters to a function, all we need to do is send a `json` body with the names of the parameters as keys. Let\'s see how! Run `uvicorn module:asymmetric` and send a `POST` request (the default `HTTP` method) to `http://127.0.0.1:8000/add`, now using the `httpx` module.\n\n```python\nimport httpx\n\npayload = {\n    "a": 48,\n    "b": 21\n}\nresponse = httpx.post("http://127.0.0.1:8000/add", json=payload)\nprint(response.json())\n```\n\nWe got a `69` response! (`48 + 21 = 69`). Of course, you can return dictionaries from your methods and those will get returned as a `json` body in the response object **automagically**!\n\nWith this in mind, you can transform any existing project into a usable API very quickly!\n\n## What about `async`?\n\nGiven that the underlying framework is `Starlette`, you can use `async` to define your methods, no problem! Here\'s an example:\n\n```py\n@asymmetric.router("/another-add")\nasync def another_async_function(a, b=372):\n    """\n    Adds :a and :b asynchronously and returns the\n    result of that operation.\n    """\n    return a + b\n```\n\n## Call me back!\n\nDon\'t you hate it when people don\'t call you back after a date? We all have lived that annoying experience. But don\'t worry! `asymmetric` **will** call you back!\n\nSome functions may be **too heavy** to be executed to respond to an `HTTP` request. Maybe your function is a predictor of some sort, and it requires an hour of processing time to spit out results. Here\'s when the `callback` parameter of the `asymmetric` decorator comes into play! You can ask `asymmetric` to terminate the `HTTP` request **immediately**, keep processing stuff and then, once it finishes, **execute a request to a specified endpoint with the results**. Let\'s imagine that we have a `predict` endpoint that we want to transform into an `API`:\n\n```python\ndef predict(data):\n    values = Model.predict(data)\n\n    # One hour later...\n    return values\n```\n\nJust add the `asymmetric` decorator and you\'re good to go!\n\n```python\n@asymmetric.router("/predict", callback=True)\ndef predict(data):\n    values = Model.predict(data)\n\n    # One hour later...\n    return values\n```\n\nOf course, if you rely on some `async` sorcery for your operations, `asymmetric` can handle it!\n\n```python\n@asymmetric.router("/predict", callback=True)\nasync def predict(data):\n    values = await Model.predict(data)\n\n    # One hour later...\n    return values\n```\n\nStart the server with `uvicorn module:asymmetric` and now you are able to call the endpoint using the following snippet:\n\n```py\nimport httpx\n\nresponse = httpx.post(\n    "http://127.0.0.1:8000/predict",\n    json={"data": mydata},\n    headers={\n        "asymmetric_callback_url": "http://callback.url/receive/predictions",\n        "asymmetric_callback_method": "post",\n    }\n)\n\nprint(response)\n```\n\nWow... **What?!** You just witnessed **the magic of `asymmetric`**. The response will be available **immediately** with a `202` status code. Meanwhile, the server will keep processing the request. When it finishes, **it will make a `POST` request to the endpoint specified in the headers** with the content of the method\'s return value. Cool, right? But what if I want to send the content of the method\'s return value inside a `json`, as the value of a `predictions` key? Well, that\'s easy! Just change the headers!\n\n```py\nimport httpx\n\nresponse = httpx.post(\n    "http://127.0.0.1:8000/predict",\n    json={"data": mydata},\n    headers={\n        "asymmetric_callback_url": "http://callback.url/receive/predictions",\n        "asymmetric_callback_method": "post",\n        "asymmetric_custom_callback_key": "predictions",\n    }\n)\n\nprint(response)\n```\n\nThat will send a `json` with one element, with `predictions` as a key and the result of the function as the value. **The key here are the headers**. They specify what to do with the result of your function. **You can also change the required headers, if you want to!**\n\n```python\ncallback_parameters = {\n    "callback_url_header": "send_me_here",\n    "callback_method_header": "use_me",\n    "custom_callback_key_header": "put_me_in_here",\n}\n\n@asymmetric.router("/predict", callback=callback_parameters)\nasync def predict(data):\n    values = await Model.predict(data)\n\n    # One hour later...\n    return values\n```\n\nNow, to achieve the same result as before, the requests must change their headers!\n\n```py\nimport httpx\n\nresponse = httpx.post(\n    "http://127.0.0.1:8000/predict",\n    json={"data": mydata},\n    headers={\n        "send_me_here": "http://callback.url/receive/predictions",\n        "use_me": "post",\n        "put_me_in_here": "predictions",\n    }\n)\n\nprint(response)\n```\n\nAs you probably imagine by now, the `callback` parameter can be a boolean or a dictionary with the following schema:\n\n```json\n{\n    "type": "object",\n    "properties": {\n        "callback_url_header": {\n            "type": "string"\n        },\n        "callback_method_header": {\n            "type": "string"\n        },\n        "custom_callback_key_header": {\n            "type": "string"\n        }\n    }\n}\n```\n\nIf no `HTTP` method is specified, the server will `POST` the information to the callback `URL`.\n\n## ~~ReDoc Documentation~~\n\n**[UNDER CONSTRUCTION]**\n\nBy default, you can `GET` the `/docs` endpoint (using a browser) to access to **interactive auto-generated documentation** about your API. It will include request bodies for each endpoint, response codes, authentication required, default values, and much more!\n\n**Tip**: Given that the [ReDoc Documentation](https://github.com/Redocly/redoc) is based on the OpenAPI standard, using **type annotations** in your code will result in a more detailed interactive documentation. Instead of the parameters being allowed to be any type, they will be forced into the type declared in your code. Cool, right?\n\n## To Do\n\n- _Automagic_ OpenAPI spec isn\'t being generated rigth now, so that\'s missing from the library. It will soon be added, though, as it is a very useful _feature_.\n- Parse callback `URL`s to make sure that they are valid `URL`s, and fail if they aren\'t.\n- On some initialization errors, the server should stop to _avoid avoidable errors_. Right now, the method to stop the server really does nothing, so that\'s something that should be addressed in the near future.\n\n## Developing\n\nClone the repository:\n\n```sh\ngit clone https://github.com/daleal/asymmetric.git\n\ncd asymmetric\n```\n\nRecreate environment:\n\n```sh\nmake get-poetry\nmake venv-with-dependencies\n```\n\n## Resources\n\n- [Official Website](https://asymmetric.one/)\n- [Issue Tracker](https://github.com/daleal/asymmetric/issues/)\n',
    'author': 'Daniel Leal',
    'author_email': 'dlleal@uc.cl',
    'maintainer': 'Daniel Leal',
    'maintainer_email': 'dlleal@uc.cl',
    'url': 'https://asymmetric.one/',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'entry_points': entry_points,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
