Package: wipac-rest-tools
rest-toolsThis project contains REST tools in python, as common code for multiple other
projects underhttps://github.com/WIPACrepo.All code uses pythonasyncio,
so is fully asyncronous.Note that both the client and server assume starting the asyncio loop
happens elsewhere - they do not start the loop themselves.ClientA REST API client exists underrest_tools.client.  Use as:fromrest_tools.clientimportRestClientapi=RestClient('http://my.site.here/api',token='XXXX')ret=awaitapi.request('GET','/fruits/apple')ret=awaitapi.request('POST','/fruits',{'name':'banana'})There are several variations of the client for OAuth2/OpenID support:OpenIDRestClient: A child ofRestClientthat supports OAuth2 token refresh using the OpenID Connect
Discovery protocol for an authentication server.ClientCredentialsAuth: UsesOpenIDRestClientin combination with OAuth2 client credentials (client ID
and secret) for service-based auth. Use this for long-lived services that
need to perform REST API calls.DeviceGrantAuth/SavedDeviceGrantAuth: UsesOpenIDRestClientto perform a "device" login for a user. Use this for
user-based terminal applications that need to perform REST API calls.
TheSavedDeviceGrantAuthcan save the refresh token to disk, allowing
repeated application sessions without having to log in again.ServerA REST API server exists underrest_tools.server. Use as:importasynciofromrest_tools.serverimportRestServer,RestHandlerclassFruits(RestHandler):defpost(self):# handle a new fruitself.write({})server=RestServer()server.add_route('/fruits',Fruits)server.startup(address='my.site.here',port=8080)asyncio.get_event_loop().run_forever()The server usesTornadoto handle HTTP
connections. It is recommended to use Apache or Nginx as a front-facing proxy,
to handle TLS sessions and non-standard HTTP requests in production.Handling Arguments Server-sideserver.ArgumentHandleris a robust wrapper aroundargparse.ArgumentParser, extended for use in handling REST arguments, both query arguments and JSON-encoded body arguments. The intended design of this class is to follow theargparsepattern as closely as possible.fromrest_tools.serverimportRestHandler,ArgumentHandler,ArgumentSourceclassFruits(RestHandler):defget(self):argo=ArgumentHandler(ArgumentSource.QUERY_ARGUMENTS,self)argo.add_argument('name',type=str)# de-facto requiredargo.add_argument('alias',dest='other_names',type=str,nargs='*',default=[])# listargo.add_argument('is-citrus',type=bool,default=False)argo.add_argument('amount',type=float,required=True)args=argo.parse_args()fruit=get_fruit(args.name,args.other_names,args.is_citrus,args.amount)...defpost(self):argo=ArgumentHandler(ArgumentSource.JSON_BODY_ARGUMENTS,self)argo.add_argument('name',type=str)# de-facto requiredargo.add_argument('other-names',type=list,default=[])argo.add_argument('supply',type=dict,required=True)def_origin(val):try:return{'USA':'United States of America','MEX':'Mexico'}[val]exceptKeyError:# raise a ValueError or TypeError to propagate a 400 ErrorraiseValueError('Invalid origin')argo.add_argument('country_code',dest='origin',type=_origin,required=True)args=argo.parse_args()add_to_basket(args.name,args.other_names,args.supply,args.origin)...