Coder Social home page Coder Social logo

fastapi-etag's Introduction

fastapi-etag

Quickstart

Basic 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 the If-None-Match header, 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.py

from fastapi import FastAPI
from starlette.requests import Request
from fastapi_etag import Etag, add_exception_handler

app = FastAPI()
add_exception_handler(app)


async def get_hello_etag(request: Request):
    return "etagfor" + request.path_params["name"]


@app.get("/hello/{name}", dependencies=[Depends(Etag(get_hello_etag))])
async def hello(name: str):
    return {"hello": name}

Run this example with uvicorn: uvicorn --port 8090 app:app

Let's break it down:

add_exception_handler(app)

The dependency raises a special CacheHit exception 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.

async def get_hello_etag(request: Request):
    name = request.path_params.get("name")
    return f"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) or None (in which case no etag header is added)

@app.get("/hello/{name}", dependencies=[Depends(Etag(get_hello_etag))])
def hello(name: str):
	...

The Etag dependency 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 the If-None-Match header, it will raise a CacheHit exception 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 added

Now including the etag in If-None-Match header (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 headers

If you want to add some extra response headers to the 304 and regular response, you can add the extra_headers argument 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 the cache-control header on all responses from the endpoint.

Contributing

See CONTRIBUTING.md

fastapi-etag's People

Contributors

nikolas-pvs avatar owinogradow avatar steinitzu avatar thomasleveil avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fastapi-etag's Issues

Suggestion: Add a LICENSE file to the repository

Hello, thanks for publishing this library!

I noticed that PyPi declares that this library is licensed with the MIT license. However, since I first discovered the package in Github, I could not find the licensing information easily.

I have understood that having a LICENSE, LICENSE.md or LICENSE.rst file in the repository root is enough to have the license name displayed on the Github repository page. Since many open source libraries do that, and it helps discovering libraries we could actually use in our projects, could you consider adding the MIT license to the repository?

Doesn't add etag when returning custom response

I don't know what the solution here is, but in most cases I use TemplateResponse or JSONResponse, and in both cases no etags are added.

from typing import Any
from fastapi_etag import Etag, add_exception_handler
from fastapi import Depends, Request, FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient

app = FastAPI()
add_exception_handler(app)


async def get_etag(request: Request) -> str:
    return "myetag"

@app.get('/foobar', dependencies=[Depends(Etag(get_etag))])
async def repos(request: Request) -> Any:
    return JSONResponse([])


client = TestClient(app)
r = client.get("/foobar")
print(r.headers)

AttributeError: 'Etag' object has no attribute 'dependency' While using fastapi-etag

I have created a file as app.py as below

from fastapi import FastAPI,Depends
from starlette.requests import Request
from fastapi_etag import Etag, add_exception_handler

app = FastAPI()
add_exception_handler(app)


async def get_hello_etag(request: Request):
    name = request.path_params.get("name")
    return f"etagfor{name}"


@app.get("/hello/{name}", dependencies=[Etag(get_hello_etag)])
def hello(name: str):
    return {"hello": name}

When I run the file uvicorn app:app --reload --host 0.0.0.0 --port 6002 I am getting below error.

Traceback (most recent call last):
  File "/usr/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
    self.run()
  File "/usr/lib/python3.7/multiprocessing/process.py", line 99, in run
    self._target(*self._args, **self._kwargs)
  File "/home/test/.local/lib/python3.7/site-packages/uvicorn/subprocess.py", line 61, in subprocess_started
    target(sockets=sockets)
  File "/home/test/.local/lib/python3.7/site-packages/uvicorn/main.py", line 382, in run
    loop.run_until_complete(self.serve(sockets=sockets))
  File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
  File "/home/test/.local/lib/python3.7/site-packages/uvicorn/main.py", line 389, in serve
    config.load()
  File "/home/test/.local/lib/python3.7/site-packages/uvicorn/config.py", line 288, in load
    self.loaded_app = import_from_string(self.app)
  File "/home/test/.local/lib/python3.7/site-packages/uvicorn/importer.py", line 20, in import_from_string
    module = importlib.import_module(module_str)
  File "/usr/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "./app.py", line 15, in <module>
    def hello(name: str):
  File "/home/test/.local/lib/python3.7/site-packages/fastapi/routing.py", line 539, in decorator
    callbacks=callbacks,
  File "/home/test/.local/lib/python3.7/site-packages/fastapi/routing.py", line 479, in add_api_route
    callbacks=callbacks,
  File "/home/test/.local/lib/python3.7/site-packages/fastapi/routing.py", line 374, in __init__
    get_parameterless_sub_dependant(depends=depends, path=self.path_format),
  File "/home/test/.local/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 118, in get_parameterless_sub_dependant
    depends.dependency
AttributeError: 'Etag' object has no attribute 'dependency' 

Please help me how to resolve the issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.