Package: asymmetric
AsymmetricThe async framework that calls you back! ✨ Enable ridiculously fast and easy module-to-APItransformations. Learn in minutes, implement in seconds. Batteries included.Why Asymmetric?Raw developing speed and ease of use, that's why.asymmetricis based onstarlette✨! Whilestarletteis 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 behindasymmetricis to be able to take any modulealready writtenand transform it into a working API in a matter of minutes, instead of having to design the module ground-up to work withstarlette(it can also be used to build an API from scratch really fast). Withasymmetric, you will also get some neat features, namely:Auto logging (configure logs with theLOG_FILEenvironmental variable).Server-side error detection and exception handling.Asynchronous callback endpointsto make a request, terminate the requestimmediatelyand then have the server make a request to acallbackendpoint with the results! ✨Auto-generated/docsand/redocendpoint for your API withinteractive documentation.Auto-generatedOpenAPI Specificationdocumentation files for your API.asymmetricis the spiritual successor tosymmetric. In fact,the base API is identical(you could probably search and replacesymmetricforasymmetricin legacy code), butasymmetricuses a more modern underlying technology to achieve better performance andsome nifty tricks. Its internal design is alsosignificantlychanged! 🎉InstallingInstall using pip!pipinstallasymmetricUsageRunning the development serverasymmetrichas its own batteries included out of the box! To start your development API server, just run:asymmetricrun<module>--reloadWhere<module>is your module name (in the examples, we will be writing in a file namedmodule.py, so the module name will be justmodule). Astarletteinstance will be spawned immediately and can be reached athttp://127.0.0.1:8000by default. We don't have any endpoints yet, so we'll add some later.All of the flags foruvicornare accepted as flags forasymmetric run.Sidenote:asymmetricis fully compatible with anyASGIserver! If you don't know what that is, don't worry! You don't need to. But if you want to use anotherASGIserver, you can! Just make sure to target theasymmetricobject! In fact, theasymmetric run <module>command is practically analiasforuvicorn <module>:asymmetric. So yes, by default the server running will beuvicorn, but, if you want to, you can also usedaphneorhypercorn!Defining the API endpointsThe module consists of a main object calledasymmetric, which includes an important element: therouterdecorator. Let's analyze it:fromasymmetricimportasymmetric@asymmetric.router("/some-route",methods=["post"],response_code=200,callback=False)The decorator recieves 4 arguments: therouteargument (the endpoint of the API to which the decorated function will map), themethodsargument (a list of the methods accepted to connect to that endpoint, defaults in onlyPOSTrequests), theresponse_codeargument (the response code of the endpoint if everything goes according to the plan. Defaults to200) and thecallbackargument (a boolean or an object specifying the request style for that endpoint, defaults toFalse, generating normal endpoint behaviour). Thecallbackattribute will have its own section below, for now we will use the defaultcallback=False.Now let's imagine that we have the following method:defsome_function():"""Greets the world."""return"Hello World!"To transform that method into an API endpoint, all you need to do is add one line:@asymmetric.router("/sample",methods=["get"])defsome_function():"""Greets the world."""return"Hello World!"Runasymmetric run module --reloadand send aGETrequest tohttp://127.0.0.1:8000/sample. You should get aHello World!in response! (To try it with a browser, make sure to run the above command and clickthis link).But what about methods with arguments? Of course they can be API'd too! Let's now say that you have the following function:defanother_function(a,b=372):"""Adds :a and :b and returns the result ofthat operation."""returna+bTo transform that method into an API endpoint, all you need to do, again, is add one line:@asymmetric.router("/add")defanother_function(a,b=372):"""Adds :a and :b and returns the result ofthat operation."""returna+bQuerying API endpointsTo give parameters to a function, all we need to do is send ajsonbody with the names of the parameters as keys. Let's see how! Runasymmetric run module --reloadand send aPOSTrequest (the defaultHTTPmethod) tohttp://127.0.0.1:8000/add, now using thehttpxmodule.importhttpxpayload={"a":48,"b":21}response=httpx.post("http://127.0.0.1:8000/add",json=payload)print(response.json())We got a69response! (48 + 21 = 69). Of course, you can return dictionaries from your methods and those will get returned as ajsonbody in the response objectautomagically!With this in mind, you can transform any existing project into a usable API very quickly!What aboutasync?Given that the underlying framework isstarlette, you can useasyncto define your methods, no problem! Here's an example:@asymmetric.router("/another-add")asyncdefanother_async_function(a,b=372):"""Adds :a and :b asynchronously and returns theresult of that operation."""returna+bCall me back!Don'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!asymmetricwillcall you back!Some functions may betoo heavyto be executed to respond to anHTTPrequest. Maybe your function is a predictor of some sort, and it requires an hour of processing time to spit out results. Here's when thecallbackparameter of theasymmetricdecorator comes into play! You can askasymmetricto terminate theHTTPrequestimmediately, keep processing stuff and then, once it finishes,execute a request to a specified endpoint with the results. Let's imagine that we have apredictendpoint that we want to transform into anAPI:defpredict(data):values=Model.predict(data)# One hour later...returnvaluesJust add theasymmetricdecorator and you're good to go!@asymmetric.router("/predict",callback=True)defpredict(data):values=Model.predict(data)# One hour later...returnvaluesOf course, if you rely on someasyncsorcery for your operations,asymmetriccan handle it!@asymmetric.router("/predict",callback=True)asyncdefpredict(data):values=awaitModel.predict(data)# One hour later...returnvaluesStart the server withasymmetric run module --reloadand now you are able to call the endpoint using the following snippet:importhttpxresponse=httpx.post("http://127.0.0.1:8000/predict",json={"data":mydata},headers={"Asymmetric-Callback-URL":"http://callback.url/receive/predictions","Asymmetric-Callback-Method":"post",})print(response)Wow...What?!You just witnessedthe magic ofasymmetric. The response will be availableimmediatelywith a202status code. Meanwhile, the server will keep processing the request. When it finishes,it will make aPOSTrequest to the endpoint specified in the headerswith 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 ajson, as the value of apredictionskey? Well, that's easy! Just change the headers!importhttpxresponse=httpx.post("http://127.0.0.1:8000/predict",json={"data":mydata},headers={"Asymmetric-Callback-URL":"http://callback.url/receive/predictions","Asymmetric-Callback-Method":"post","Asymmetric-Custom-Callback-Key":"predictions",})print(response)That will send ajsonwith one element, withpredictionsas 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!callback_parameters={"callback_url_header":"Send-Me-Here","callback_method_header":"Use-Me","custom_callback_key_header":"Put-Me-In-Here",}@asymmetric.router("/predict",callback=callback_parameters)asyncdefpredict(data):values=awaitModel.predict(data)# One hour later...returnvaluesNow, to achieve the same result as before, the requests must change their headers!importhttpxresponse=httpx.post("http://127.0.0.1:8000/predict",json={"data":mydata},headers={"Send-Me-Here":"http://callback.url/receive/predictions","Use-Me":"post","Put-Me-In-Here":"predictions",})print(response)As you probably imagine by now, thecallbackparameter can be a boolean or a dictionary with the followingpseudo-schema:{"callback_url_header":{"required":False,"type":str,},"callback_method_header":{"required":False,"type":str,},"custom_callback_key_header":{"required":False,"type":str,},}If noHTTPmethod is specified, the server willPOSTthe information to the callbackURL.ReDoc/Swagger DocumentationBy default, you canGETthe/docsor the/redocendpoints (using a browser) to access tointeractive auto-generated documentationabout your API. It will include request bodies for each endpoint, response codes, headers required, default values, and much more!Tip: Given that theReDoc Documentationand theSwaggerUI Documentationare based on the OpenAPI standard, usingtype annotationsin 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?You can also get ajsonfile containing the OpenAPI spec of your API by running the following command:asymmetricdocsmoduleYou can also use the flag--filenameto specify the name of the output file. It defaults toopenapi.json.To DoParse callbackURLs to make sure that they are validURLs, and fail if they aren't.DevelopingClone the repository:gitclonehttps://github.com/daleal/asymmetric.gitcdasymmetricRecreate environment:makeget-poetry
makevenv-with-dependenciesRun the linters:makelintersRun the tests:maketestsAknowledgementsThis project wouldn't be possible withoutEncode, as literally all hard dependencies (starletteandhttpx) are developed by them. Evenuvicornis theirs. Thanks guys!asymmetricalso takes inspiration fromFastAPIon some implementation details, so thank you too for such an awesome project. Also, I want to thankevery personwho has given me some kind of advice or that has had to listen to me going on and onand onabout this project and the reasons that make itgreat. Finally, thank you! Yes, you! If you are reading this, there is a good chance that you have at least some interest inasymmetric. If you have feedback, if you find a bug, if you want to ask for afeatureor even if you just want to talk, I'd be glad to hear from you! You're the reason this project exists ♥.ResourcesIssue Tracker