Package: fastapi-etag
fastapi-etagQuickstartBasic etag support for FastAPI, allowing you to benefit from conditional caching in web browsers and reverse-proxy caching layers.This does not generate etags that are a hash of the response content, but instead lets you pass in a custom etag generating function per endpoint that is called before executing the route function.This lets you bypass expensive API calls when client includes a matching etag in theIf-None-Matchheader, in this case your endpoint is never called, instead returning a 304 response telling the client nothing has changed.The etag logis is implemented with a fastapi dependency that you can add to your routes or entire routers.Here's how you use it:# app.pyfromfastapiimportFastAPIfromstarlette.requestsimportRequestfromfastapi_etagimportEtag,add_exception_handlerapp=FastAPI()add_exception_handler(app)asyncdefget_hello_etag(request:Request):return"etagfor"+request.path_params["name"]@app.get("/hello/{name}",dependencies=[Depends(Etag(get_hello_etag))])asyncdefhello(name:str):return{"hello":name}Run this example withuvicorn: uvicorn --port 8090 app:appLet's break it down:add_exception_handler(app)The dependency raises a specialCacheHitexception to exit early when there's a an etag match, this adds a standard exception handler to the app to generate a correct 304 response from the exception.asyncdefget_hello_etag(request:Request):name=request.path_params.get("name")returnf"etagfor{name}"This is the function that generates the etag for your endpoint.It can do anything you want, it could for example return a hash of a last modified timestamp in your database.It can be either a normal function or an async function.Only requirement is that it accepts one argument (request) and that it returns either a string (the etag) orNone(in which case no etag header is added)@app.get("/hello/{name}",dependencies=[Depends(Etag(get_hello_etag))])defhello(name:str):...TheEtagdependency is called like any fastapi dependency.
It always adds the etag returned by your etag gen function to the response.If client passes a matching etag in theIf-None-Matchheader, it will raise aCacheHitexception which triggers a 304 response before calling your endpoint.Now try it with curl:curl -i "http://localhost:8090/hello/bob"
HTTP/1.1 200 OK
date: Mon, 30 Dec 2019 21:55:43 GMT
server: uvicorn
content-length: 15
content-type: application/json
etag: W/"etagforbob"

{"hello":"bob"}Etag header is addedNow including the etag inIf-None-Matchheader (mimicking a web browser):curl -i -X GET "http://localhost:8090/hello/bob" -H "If-None-Match: W/\"etagforbob\""
HTTP/1.1 304 Not Modified
date: Mon, 30 Dec 2019 21:57:37 GMT
server: uvicorn
etag: W/"etagforbob"It now returns no content, only the 304 telling us nothing has changed.Add response headersIf you want to add some extra response headers to the 304 and regular response,
you can add theextra_headersargument with a dict of headers:@app.get(
    "/hello/{name}",
    dependencies=[
        Depends(
            Etag(
                get_hello_etag,
                extra_headers={"Cache-Control": "public, max-age: 30"},
            )
        )
    ],
)
def hello(name: str):
	...This will add thecache-controlheader on all responses from the endpoint.ContributingSeeCONTRIBUTING.md