From e58ab1b8f6e4545b71df72c0b868920e3c643bbd Mon Sep 17 00:00:00 2001 From: Andrew Brookins Date: Tue, 9 Nov 2021 08:05:20 -0800 Subject: [PATCH] FastAPI integration example --- docs/fastapi_integration.md | 133 ++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/fastapi_integration.md diff --git a/docs/fastapi_integration.md b/docs/fastapi_integration.md new file mode 100644 index 0000000..3994155 --- /dev/null +++ b/docs/fastapi_integration.md @@ -0,0 +1,133 @@ +# FastAPI Integration + +## Introduction + +This section includes a complete example showing how to integrate Redis OM with FastAPI. + +Good news: Redis OM was **specifically designed to integrate with FastAPI**! + +## Concepts + +### Every Redis OM Model is also a Pydantic model + +Every Redis OM model is also a Pydantic model, so you can define a model and then pass the model class into any location that FastAPI expects a Pydantic model. + +This means a couple of things: + +1. A Redis OM model can be used for request body validation +2. Redis OM models show up in the auto-generated API documentation + +### Cache vs. Data + +Redis works well as either a durable data store or a cache, but the optiomal Redis configuration is often different between these two use cases. + +You almost always want to use a Redis instance tuned for caching when you're caching and a separate Redis instance tuned for data durability for storing application state. + +This example shows how to manage these two uses of Redis within the same application. The app uses a FastAPI caching framework and dedicated caching instance of Redis for caching, and a separate Redis instance tuned for durability for Redis OM models. + + +## Example app code + +This is a complete example that you can run as-is: + +```python +import datetime +from typing import Optional + +import aioredis + +from fastapi import FastAPI, HTTPException +from starlette.requests import Request +from starlette.responses import Response + +from fastapi_cache import FastAPICache +from fastapi_cache.backends.redis import RedisBackend +from fastapi_cache.decorator import cache + +from pydantic import EmailStr + +from redis_om.model import HashModel, NotFoundError +from redis_om.connections import get_redis_connection + +# This Redis instance is tuned for durability. +REDIS_DATA_URL = "redis://localhost:6380" + +# This Redis instance is tuned for cache performance. +REDIS_CACHE_URL = "redis://localhost:6381" + + +class Customer(HashModel): + first_name: str + last_name: str + email: EmailStr + join_date: datetime.date + age: int + bio: Optional[str] + + +app = FastAPI() + + +@app.post("/customer") +async def save_customer(customer: Customer): + # We can save the model to Redis by calling `save()`: + return customer.save() + + +@app.get("/customers") +async def list_customers(request: Request, response: Response): + # To retrieve this customer with its primary key, we use `Customer.get()`: + return {"customers": Customer.all_pks()} + + +@app.get("/customer/{pk}") +@cache(expire=10) +async def get_customer(pk: str, request: Request, response: Response): + # To retrieve this customer with its primary key, we use `Customer.get()`: + try: + return Customer.get(pk) + except NotFoundError: + raise HTTPException(status_code=404, detail="Customer not found") + + +@app.on_event("startup") +async def startup(): + r = aioredis.from_url(REDIS_CACHE_URL, encoding="utf8", decode_responses=True) + FastAPICache.init(RedisBackend(r), prefix="fastapi-cache") + + # You can set the Redis OM URL using the REDIS_OM_URL environment + # variable, or by manually creating the connection using your model's + # Meta object. + Customer.Meta.database = get_redis_connection(url=REDIS_DATA_URL, decode_responses=True) +``` + +## Testing the app + +You should install the app's dependencies first. This app uses Poetry, so you'll want to make sure you have that installed first: + + $ pip install poetry + +Then install the dependencies: + + $ poetry install + +Next, start the server: + + $ poetry run uvicorn --reload main:test + +Then, in another shell, create a customer: + + $ curl -X POST -H 'Content-Length: 0' "http://localhost:8000/customer" + $ curl -X POST "http://localhost:8000/customer" -H 'Content-Type: application/json' -d '{"first_name":"Andrew","last_name":"Brookins","email":"a@example.com","age":"38","join_date":"2020 +-01-02"}' + {"pk":"01FM2G8EP38AVMH7PMTAJ123TA","first_name":"Andrew","last_name":"Brookins","email":"a@example.com","join_date":"2020-01-02","age":38,"bio":""} + +Get a copy of the value for "pk" and make another request to get that customer: + + $ curl "http://localhost:8000/customer/01FM2G8EP38AVMH7PMTAJ123TA" + {"pk":"01FM2G8EP38AVMH7PMTAJ123TA","first_name":"Andrew","last_name":"Brookins","email":"a@example.com","join_date":"2020-01-02","age":38,"bio":""} + +You can also get a list of all customer PKs: + + $ curl "http://localhost:8000/customers" + {"customers":["01FM2G8EP38AVMH7PMTAJ123TA"]} \ No newline at end of file