{"id":30950750,"url":"https://github.com/daleal/asymmetric","last_synced_at":"2025-09-11T05:18:47.516Z","repository":{"id":48933997,"uuid":"305593506","full_name":"daleal/asymmetric","owner":"daleal","description":"Ridiculously fast and easy module-to-API transformations. Learn in minutes, implement in seconds. Batteries included.","archived":false,"fork":false,"pushed_at":"2021-07-05T02:09:16.000Z","size":111,"stargazers_count":36,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-30T05:38:17.031Z","etag":null,"topics":["apis","async-framework","callback-endpoint","httpx","openapi","pip","python","python3","redoc","starlette","swagger-ui","uvicorn"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daleal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-20T04:52:12.000Z","updated_at":"2024-02-11T13:32:46.000Z","dependencies_parsed_at":"2022-09-04T02:01:34.734Z","dependency_job_id":null,"html_url":"https://github.com/daleal/asymmetric","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/daleal/asymmetric","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daleal%2Fasymmetric","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daleal%2Fasymmetric/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daleal%2Fasymmetric/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daleal%2Fasymmetric/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daleal","download_url":"https://codeload.github.com/daleal/asymmetric/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daleal%2Fasymmetric/sbom","scorecard":{"id":317597,"data":{"date":"2025-08-11","repo":{"name":"github.com/daleal/asymmetric","commit":"8d1f5c4d9a5b9a9f0013952c847caff9d9958061"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:62: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:84: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:87: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:106: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linters.yml:109: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/linters.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:63: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:69: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/tests.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tests.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/daleal/asymmetric/tests.yml/master?enable=pin","Info:   0 out of  17 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/linters.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 23 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"13 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6","Warn: Project is vulnerable to: PYSEC-2022-42986 / GHSA-43fp-rhv2-5gv8","Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: GHSA-vqfr-h8mv-ghfj","Warn: Project is vulnerable to: PYSEC-2022-183 / GHSA-h8pj-cxx2-jfg2","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: PYSEC-2020-92 / GHSA-hj5v-574p-mj7c","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: GHSA-2c2j-9gv5-cj73","Warn: Project is vulnerable to: PYSEC-2023-48 / GHSA-74m5-2c7w-9w3x","Warn: Project is vulnerable to: GHSA-f96h-pmfr-66vw","Warn: Project is vulnerable to: PYSEC-2023-83 / GHSA-v5gw-mw7f-84px","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T00:35:21.486Z","repository_id":48933997,"created_at":"2025-08-18T00:35:21.486Z","updated_at":"2025-08-18T00:35:21.486Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274532380,"owners_count":25303325,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-10T02:00:12.551Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apis","async-framework","callback-endpoint","httpx","openapi","pip","python","python3","redoc","starlette","swagger-ui","uvicorn"],"created_at":"2025-09-11T05:18:44.763Z","updated_at":"2025-09-11T05:18:47.508Z","avatar_url":"https://github.com/daleal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/daleal/asymmetric\"\u003e\n        \u003cimg src=\"https://asymmetric.daleal.dev/assets/asymmetric-250x250.png\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eAsymmetric\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cem\u003e\n        Ridiculously fast and easy module-to-API transformations. Learn in minutes, implement in seconds. Batteries included.\n    \u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://pypi.org/project/asymmetric\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/v/asymmetric?label=version\u0026logo=python\u0026logoColor=%23fff\u0026color=306998\" alt=\"PyPI - Version\"\u003e\n\u003c/a\u003e\n\n\u003ca href=\"https://github.com/daleal/asymmetric/actions?query=workflow%3Atests\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/workflow/status/daleal/asymmetric/tests?label=tests\u0026logo=python\u0026logoColor=%23fff\" alt=\"Tests\"\u003e\n\u003c/a\u003e\n\n\u003ca href=\"https://codecov.io/gh/daleal/asymmetric\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/codecov/c/gh/daleal/asymmetric?label=coverage\u0026logo=codecov\u0026logoColor=ffffff\" alt=\"Coverage\"\u003e\n\u003c/a\u003e\n\n\u003ca href=\"https://github.com/daleal/asymmetric/actions?query=workflow%3Alinters\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/workflow/status/daleal/asymmetric/linters?label=linters\u0026logo=github\" alt=\"Linters\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\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 (configure logs with the `LOG_FILE` environmental variable).\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` and `/redoc` endpoint for your API with **interactive documentation**.\n- Auto-generated [OpenAPI Specification](https://swagger.io/docs/specification/about/) documentation files for your API.\n\n`asymmetric` is the spiritual successor to [`symmetric`](https://github.com/daleal/symmetric). In fact, **the base API is identical** (you could probably search and replace `symmetric` for `asymmetric` in legacy code), but `asymmetric` uses a more modern underlying technology to achieve better performance and [**some nifty tricks**](#call-me-back). Its internal design is also **significantly** changed! 🎉\n\n## Installing\n\nInstall using pip!\n\n```sh\npip install asymmetric\n```\n\n## Usage\n\n### Running the development server\n\n`asymmetric` has its own batteries included out of the box! To start your development API server, just run:\n\n```sh\nasymmetric run \u003cmodule\u003e --reload\n```\n\nWhere `\u003cmodule\u003e` 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. **All of the flags for `uvicorn` are accepted as flags for `asymmetric run`**.\n\n**Sidenote**: `asymmetric` is fully compatible with any `ASGI` server! If you don't know what that is, don't worry! You don't need to. But if you want to use another `ASGI` server, you can! Just make sure to target the `asymmetric` object! In fact, the `asymmetric run \u003cmodule\u003e` command is practically an _alias_ for `uvicorn \u003cmodule\u003e:asymmetric`. So yes, by default the server running will be `uvicorn`, but, if you want to, you can also use `daphne` or `hypercorn`!\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 `asymmetric run module --reload` 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 `asymmetric run module --reload` 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 `asymmetric run module --reload` 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 _pseudo-schema_:\n\n```python\n{\n    \"callback_url_header\": {\n        \"required\": False,\n        \"type\": str,\n    },\n    \"callback_method_header\": {\n        \"required\": False,\n        \"type\": str,\n    },\n    \"custom_callback_key_header\": {\n        \"required\": False,\n        \"type\": str,\n    },\n}\n```\n\nIf no `HTTP` method is specified, the server will `POST` the information to the callback `URL`.\n\n## ReDoc/Swagger Documentation\n\nBy default, you can `GET` the `/docs` or the `/redoc` endpoints (using a browser) to access to **interactive auto-generated documentation** about your API. It will include request bodies for each endpoint, response codes, headers required, default values, and much more!\n\n**Tip**: Given that the [ReDoc Documentation](https://github.com/Redocly/redoc) and the [SwaggerUI Documentation](https://swagger.io/tools/swagger-ui/) are 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\nYou can also get a `json` file containing the OpenAPI spec of your API by running the following command:\n\n```sh\nasymmetric docs module\n```\n\nYou can also use the flag `--filename` to specify the name of the output file. It defaults to `openapi.json`.\n\n## To Do\n\n- Parse callback `URL`s to make sure that they are valid `URL`s, and fail if they aren't.\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\nRun the linters:\n\n```sh\nmake linters\n```\n\nRun the tests:\n\n```sh\nmake tests\n```\n\n## Aknowledgements\n\nThis project wouldn't be possible without [Encode](https://github.com/encode), as literally all hard dependencies (`starlette` and `httpx`) are developed by them. Even `uvicorn` is theirs. Thanks guys! `asymmetric` also takes inspiration from [`FastAPI`](https://github.com/tiangolo/fastapi) on some implementation details, so thank you too for such an awesome project. Also, I want to thank **every person** who has given me some kind of advice or that has had to listen to me going on and on **and on** about this project and the reasons that make it **great**. Finally, thank you! Yes, you! If you are reading this, there is a good chance that you have at least some interest in `asymmetric`. If you have feedback, if you find a bug, if you want to ask for a _feature_ or even if you just want to talk, I'd be glad to hear from you! You're the reason this project exists ♥.\n\n## Resources\n\n- [Issue Tracker](https://github.com/daleal/asymmetric/issues/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaleal%2Fasymmetric","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaleal%2Fasymmetric","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaleal%2Fasymmetric/lists"}