Package: aiohttp-deps
AioHTTP depsThis project was initially created to show the abillities oftaskiq-dependenciesproject, which is used bytaskiqto provide you with the best experience of sending distributed tasks.This project addsFastAPI-like dependency injection to yourAioHTTPapplication and swagger documentation based on types.To start using dependency injection, just initialize the injector.fromaiohttpimportwebfromaiohttp_depsimportinitasdeps_initapp=web.Application()app.on_startup.append(deps_init)web.run_app(app)If you use mypy, then we have a custom router with propper types.fromaiohttpimportwebfromaiohttp_depsimportinitasdeps_initfromaiohttp_depsimportRouterrouter=Router()@router.get("/")asyncdefhandler():returnweb.json_response({})app=web.Application()app.router.add_routes(router)app.on_startup.append(deps_init)web.run_app(app)Also, you can nest routers with prefixes,api_router=Router()memes_router=Router()main_router=Router()main_router.add_routes(api_router,prefix="/api")main_router.add_routes(memes_router,prefix="/memes")SwaggerIf you use dependencies in you handlers, we can easily generate swagger for you.
We have some limitations:We don't support resolving type aliases if hint is a string.
If you define variable like this:myvar = int | Noneand then in handler
you'd create annotation like this:param: "str | myvar"it will fail.
You need to unquote type hint in order to get it work.We will try to fix these limitations later.To enable swagger, just add it to your startup.fromaiohttp_depsimportinit,setup_swaggerapp=web.Application()app.on_startup.extend([init,setup_swagger()])ResponsesYou can define schema for responses using dataclasses or
pydantic models. This would not affect handlers in any way,
it's only for documentation purposes, if you want to actually
validate values your handler returns, please write your own wrapper.fromdataclassesimportdataclassfromaiohttpimportwebfrompydanticimportBaseModelfromaiohttp_depsimportRouter,openapi_responserouter=Router()@dataclassclassSuccess:data:strclassUnauthorized(BaseModel):why:str@router.get("/")@openapi_response(200,Success,content_type="application/xml")@openapi_response(200,Success)@openapi_response(401,Unauthorized,description="When token is not correct")asyncdefhandler()->web.Response:...This example illustrates how much you can do with this decorator. You
can have multiple content-types for a single status, or you can have different
possble statuses. This function is pretty simple and if you want to make
your own decorator for your responses, it won't be hard.Default dependenciesBy default this library provides only two injectables.web.Requestandweb.Application.asyncdefhandler(app:web.Application=Depends()):...asyncdefhandler2(request:web.Request=Depends()):...It's super useful, because you can use these dependencies in
any other dependency. Here's a more complex example of how you can use this library.fromaiohttp_depsimportRouter,Dependsfromaiohttpimportwebrouter=Router()asyncdefget_db_session(app:web.Application=Depends()):asyncwithapp[web.AppKey("db")]assess:yieldsessclassMyDAO:def__init__(self,session=Depends(get_db_session)):self.session=sessionasyncdefget_objects(self)->list[object]:returnawaitself.session.execute("SELECT 1")@router.get("/")asyncdefhandler(db_session:MyDAO=Depends()):objs=awaitdb_session.get_objects()returnweb.json_response({"objects":objs})If you do something like this, you would never think about initializing your DAO. You can just inject it and that's it.Built-in dependenciesThis library also provides you with some default dependencies that can help you in building the best web-service.JsonTo parse json, create a pydantic model and add a dependency to your handler.fromaiohttpimportwebfrompydanticimportBaseModelfromaiohttp_depsimportRouter,Json,Dependsrouter=Router()classUserInfo(BaseModel):name:str@router.post("/users")asyncdefnew_data(user:UserInfo=Depends(Json())):returnweb.json_response({"user":user.model_dump()})This dependency automatically validates data and send
errors if the data doesn't orrelate with schema or body is not a valid json.If you want to make this data optional, just mark it as optional.@router.post("/users")asyncdefnew_data(user:Optional[UserInfo]=Depends(Json())):ifuserisNone:returnweb.json_response({"user":None})returnweb.json_response({"user":user.model_dump()})HeadersYou can get and validate headers usingHeaderdependency.Let's try to build simple example for authorization.fromaiohttp_depsimportRouter,Header,Dependsfromaiohttpimportwebrouter=Router()defdecode_token(authorization:str=Depends(Header()))->str:ifauthorization=="secret":# Let's pretend that here we# decode our token.returnauthorizationraiseweb.HTTPUnauthorized()@router.get("/secret_data")asyncdefnew_data(token:str=Depends(decode_token))->web.Response:returnweb.json_response({"secret":"not a secret"})As you can see, header name to parse is equal to the
name of a parameter that introduces Header dependency.If you want to use some name that is not allowed in python, or just want to have different names, you can use alias. Like this:defdecode_token(auth:str=Depends(Header(alias="Authorization")))->str:Headers can also be parsed to types. If you want a header to be parsed as int, just add the typehint.defdecode_token(meme_id:int=Depends(Header()))->str:If you want to get list of values of one header, use parametermultiple=True.defdecode_token(meme_id:list[int]=Depends(Header(multiple=True)))->str:And, of course, you can provide this dependency with default value if the value from user cannot be parsed for some reason.defdecode_token(meme_id:str=Depends(Header(default="not-a-secret")))->str:QueriesYou can depend onQueryto get and parse query parameters.fromaiohttp_depsimportRouter,Query,Dependsfromaiohttpimportwebrouter=Router()@router.get("/shop")asyncdefshop(item_id:str=Depends(Query()))->web.Response:returnweb.json_response({"id":item_id})the name of the parameter is the same as the name of function parameter.The Query dependency is acually the same as the Header dependency, so everything about theHeaderdependency also applies toQuery.ViewsIf you use views as handlers, please use View class fromaiohttp_deps, otherwise the magic won't work.fromaiohttp_depsimportRouter,View,Dependsfromaiohttpimportwebrouter=Router()@router.view("/view")classMyView(View):asyncdefget(self,app:web.Application=Depends()):returnweb.json_response({"app":str(app)})FormsNow you can easiy get and validate form data from your request.
To make the magic happen, please addarbitrary_types_allowedto the config of your model.importpydanticfromaiohttp_depsimportRouter,Depends,Formfromaiohttpimportwebrouter=Router()classMyForm(pydantic.BaseModel):id:intfile:web.FileFieldmodel_config=pydantic.ConfigDict(arbitrary_types_allowed=True)@router.post("/")asyncdefhandler(my_form:MyForm=Depends(Form())):withopen("my_file","wb")asf:f.write(my_form.file.file.read())returnweb.json_response({"id":my_form.id})PathIf you have path variables, you can also inject them in your handler.fromaiohttp_depsimportRouter,Path,Dependsfromaiohttpimportwebrouter=Router()@router.get("/view/{var}")asyncdefmy_handler(var:str=Depends(Path())):returnweb.json_response({"var":var})Overridiing dependenciesSometimes for tests you don't want to calculate actual functions
and you want to pass another functions instead.To do so, you can add "dependency_overrides" or "values_overrides" to the aplication's state.
These values should be dicts. The keys for these values can be found inaiohttp_deps.keysmodule.Here's an example.deforiginal_dep()->int:return1classMyView(View):asyncdefget(self,num:int=Depends(original_dep)):"""Nothing."""returnweb.json_response({"request":num})Imagine you have a handler that depends on some function,
but instead of1you want to have2in your tests.To do it, jsut adddependency_overridessomewhere,
where you create your application. And make sure that keys
of that dict are actual function that are being replaced.fromaiohttp_depsimportVALUES_OVERRIDES_KEYmy_app[VALUES_OVERRIDES_KEY]={original_dep:2}Butvalues_overridesonly overrides returned values. If you want to
override functions, you have to usedependency_overrides. Here's an example:fromaiohttp_depsimportDEPENDENCY_OVERRIDES_KEYdefreplacing_function()->int:return2my_app[DEPENDENCY_OVERRIDES_KEY]={original_dep:replacing_function}The cool point aboutdependency_overrides, is that it recalculates graph and
you can use dependencies in function that replaces the original.