The router is will automatically route based structure of your files.
There is no need to manage lists of routes matched to files or exports; all that is
required is that you create a file in the location or pattern configured to hold your endpoints and the router will
automatically find it.
Examples
Don't like reading documentation? Then look at
our examples, which can run locally!
fromacai_aws.apigateway.routerimportRouterrouter=Router(base_path='your-service/v1',handlers='api/handlers',schema='api/openapi.yml',cors=True,# default Trueauto_validate=True,# default Falsevalidate_response=True,# default Falseverbose_logging=True,# default Falsetimeout=25,# time in seconds, ints only,output_error=False,# default True;cache_mode='all',# static-only | dynamic-only ; all is defaultcache_size=512,# 128 is default; use None to disable cachebefore_all=before_all_example,after_all=after_all_example,on_error=on_error_example,with_auth=with_auth_example,on_timeout=on_timeout_example)router.auto_load()defroute(event,context):returnrouter.route(event,context)# requirements = kwargs from @requirements decorator; default: {}defbefore_all_example(request,response,requirements):pass# requirements = kwargs from @requirements decorator; default: {}defafter_all_example(request,response,requirements):pass# requirements = kwargs from @requirements decorator; default: {}defwith_auth_example(request,response,requirements):pass# error is the exception object raiseddefon_error_example(request,response,error):pass# error is the exception object raiseddefon_timeout_example(request,response,error):pass
There are three ways to organize your routes: directory, pattern and mapping; directory and pattern routing
mode requires your project files to be placed in a particular way; mapping does not require any structure, as you
define every route, and it's a corresponding file. Below are the three ways configure your router:
It may be more maintainable to store your routes list in a separate file, this example does not have that for brevity
Warning
Even though you are matching your files to your routes, the handler files must have functions that match HTTP method (see endpoint examples here)
Danger
This is not the preferred routing mode to use; this can lead to a sloppy, unpredictable project architecture which will be hard to maintain and extend. This is NOT RECOMMENDED.
Each endpoint is meant to be treated as a separate module within the API. These endpoints are not meant to be extended
or commingled and thus should approach individually. If resources are meant to be shared across endpoints, then
those resources should be packaged as shared classes or utilities.
Every endpoint file should contain a function which matches an
HTTP method in lower case.
Most common are post, get, put, patch, delete, but this library does support custom methods,
if you so choose. As long as the method of the request matches the function name, it will work.
Each method within the endpoint file can have individual validation requirements. These requirements allow you to test
all structural points of the request, with the ability to use JSONSchema and custom middleware to further extend the
validation options.
Info
See the full configuration list, explanation and example of each setting in our
Configurations Section.
Tip
If you are already using an openapi.yml, none of these requirements below are necessary. Ensure your router has
enabled auto_validate
with proper schema configured and the below requirements are not necessary for any basic structural validation
(headers, body, query, params will be checked via openapi.yml). You can still use before, after & data_class with
other custom validations for more advanced use cases.
# example for endpoint file: api/grower.pyfromacai_aws.apigateway.requirementsimportrequirementsfromacai_aws.common.loggerimportloggerfromapi.logic.growerimportGrowerfromapi.logic.middlwareimportlog_grower,filter_grower# example after functiondeffilter_grower(request,response,requirements):if'GET'inresponse.raw['message']:logger.log(log=response.raw)@requirements(required_query=['requester_id'],available_query=['grower_id','grower_email'],data_class=Grower,after=filter_grower,auth_required=True)defget(request,response):response.body={'message':'GET called','request_query_params':request.query_params}returnresponse# example before functiondeflog_grower(request,response,requirements):logger.log(log=request.body['grower_id'])@requirements(required_body='v1-grower-post-request',before=log_grower,auth_required=True)defpost(request,response):response.body={'message':'POST called','request_body':request.body}returnresponse@requirements(required_headers=['x-api-key','x-correlation-id']required_route='grower/{grower_id}'auth_required=Truerequired_body={'type':'object','required':['grower_id'],'additionalProperties':False,'properties':{'grower_id':{'type':'string'},'body':{'type':'object'},'dict':{'type':'boolean'}}})defpatch(request,response):response.body={'message':'PATCH called','request_body':request.body}returnresponse@requirements(timeout=20)# this will override timeout set in router.pydefput(request,response):response.body={'message':'PUT called'}returnresponse# requirements is not requireddefdelete(request,response):response.body={'message':'DELETE called'}returnresponse
This is not recommended for frequent use as it raises errors for every header which does not conform to the array provided. Many browsers, http tools, and libraries will automatically add headers to request, unbeknownst to the user. By using this setting, you will force every user of the endpoint to take extra care with the headers provided and may result in poor API consumer experience.
This is referencing a components.schemas section of your openapi.yml file defined in the schema value in your router config, but you can also pass in a json schema in the form of a dict.
This is referencing a components.schemas section of your openapi.yml file defined in the schema value in your router config, but you can also pass in a json schema in the form of a dict.
You can add as many custom requirements as you want, with any variable type you want, and they will be passed to
your before_all, before, after_all, after and with_auth middleware defined functions.
By default, every endpoint function will receive an instance of the Request class (aka request) as the first
argument of their function. This request has a lot of properties which will do common things automatically, but
still allows the developer to override those operations if they deem necessary. Below is a list and examples of all
the properties of the request:
This is the safest way to get the body of the request. It will use the content-type header to determine the data sent and convert it; if the data can't be converted for whatever reason it will catch the error and return the raw body provided unconverted.
This is the original full request. Not advisable to use this as defeats the purpose of the entire Acai . In addition, you don't want to mutate this object and potentially mess up the entire router.
By default, every endpoint function will receive an instance of the Response class (aka response) as the second argument of their function.
This response object is meant to provide consistency to HTTP response codes and error signatures. Below is a list and examples of all the properties of the response:
This will NOT automatically convert the body to json if possible when called. This is great when working with an after_all method that wants to mutate the body of the response before returning to the user.
some_key='abc123'response.set_error('someKey',f'{some_key} is not a valid key to use with this service; try again with a different key')another_key='def456'response.set_error('anotherKey',f'{another_key} is not the correct type to operate on')print(response.raw)# output:{'errors':[{'key_path':'someKey','message':'abc123 is not a valid key to use with this service; try again with a different key'},{'key_path':'anotherKey','message':'def456 is not the correct type to operate on'}]}
response.setError('user','your access is denied')print(response.has_error)# output:Trueresponse.body={'user':'you have been granted access'};print(response.has_error)# output:False
You can generate a openapi yaml and/or json doc from your existing codebase. This feature can also add to existing openapi docs and/or overwrite
incorrect documentation.