Skip to content

Commit 7261e9b

Browse files
committed
setting up makefile and logging
1 parent f3985b1 commit 7261e9b

File tree

8 files changed

+269
-8
lines changed

8 files changed

+269
-8
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
LOG_LEVEL=INFO
12
PORT=8000
23
REDIS_URL="redis://localhost:6379"
34

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
install:
2-
@uv sync
2+
@uv sync --all-extras
33

44
dev: install
55
@fastapi dev app/main.py
@@ -23,4 +23,4 @@ lock: install
2323
@uv lock
2424

2525
clean:
26-
@rm -rf .venv/ .mypy_cache/ .ruff_cache/
26+
@rm -rf .venv/ .mypy_cache/ .ruff_cache/

app/logger.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import logging
2+
import os
3+
4+
logger = logging.getLogger("uvicorn")
5+
logger.setLevel(os.environ.get("LOG_LEVEL", logging.INFO))

app/main.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
1-
from typing import Union
1+
from dotenv import load_dotenv
2+
load_dotenv()
3+
4+
import os
5+
from contextlib import asynccontextmanager
6+
from typing import Any, AsyncIterator, Never, Union
27

38
from fastapi import FastAPI
49

5-
app = FastAPI()
10+
import app.redis as redis
11+
from app.todos import Todos
12+
13+
redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
14+
client = redis.get_client(redis_url)
15+
todos = Todos(client)
16+
17+
18+
@asynccontextmanager
19+
async def lifespan(_: FastAPI) -> AsyncIterator[Never]:
20+
# before
21+
await todos.initialize()
22+
yield # type: ignore
23+
# after
24+
return
25+
26+
27+
app = FastAPI(lifespan=lifespan)
628

729

830
@app.get("/")
9-
def read_root():
10-
return {"Hello": "World"}
31+
async def read_root() -> bool:
32+
return await todos.have_index()
1133

1234

1335
@app.get("/items/{item_id}")
14-
def read_item(item_id: int, q: Union[str, None] = None):
36+
def read_item(item_id: int, q: Union[str, None] = None) -> Any:
1537
return {"item_id": item_id, "q": q}

app/redis.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Optional
2+
3+
from redis.asyncio import Redis
4+
from redis.backoff import ExponentialBackoff
5+
from redis.exceptions import ConnectionError, TimeoutError
6+
from redis.retry import Retry
7+
8+
from app.logger import logger
9+
10+
client: Optional[Redis] = None
11+
12+
13+
def get_client(url: str = "redis://localhost:6379") -> Redis:
14+
global client
15+
16+
if client is None:
17+
logger.info(f"Creating redis client for url: {url}")
18+
client = Redis.from_url(
19+
url,
20+
decode_responses=True,
21+
retry=Retry(ExponentialBackoff(cap=10, base=1), 25),
22+
retry_on_error=[ConnectionError, TimeoutError, ConnectionResetError],
23+
health_check_interval=1,
24+
)
25+
26+
return client

app/todos.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from redis.asyncio import Redis
2+
from redis.commands.search.field import TextField
3+
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
4+
from redis.exceptions import ResponseError
5+
6+
from app.logger import logger
7+
8+
TODOS_INDEX = "todos-idx"
9+
TODOS_PREFIX = "todos:"
10+
11+
12+
class Todos:
13+
"""Stores todos"""
14+
15+
def __init__(self, redis: Redis):
16+
self.redis = redis
17+
18+
async def initialize(self) -> None:
19+
await self.create_index_if_not_exists()
20+
return None
21+
22+
async def have_index(self) -> bool:
23+
try:
24+
await self.redis.ft(TODOS_INDEX).info() # type: ignore
25+
except ResponseError as e:
26+
if "Unknown index name" not in str(e):
27+
logger.info(f"Index {TODOS_INDEX} already exists")
28+
raise
29+
30+
return True
31+
32+
async def create_index_if_not_exists(self) -> None:
33+
try:
34+
if await self.have_index():
35+
return None
36+
37+
logger.debug(f"Creating index {TODOS_INDEX}")
38+
39+
schema = (
40+
TextField("$.name", as_name="name"),
41+
TextField("$.status", as_name="status"),
42+
)
43+
44+
await self.redis.ft(TODOS_INDEX).create_index( # type: ignore
45+
schema,
46+
definition=IndexDefinition( # type: ignore
47+
prefix=[TODOS_PREFIX], index_type=IndexType.JSON
48+
),
49+
)
50+
51+
logger.debug(f"Index {TODOS_INDEX} created successfully")
52+
except Exception as e:
53+
logger.error(f"Error setting up index {TODOS_INDEX}: {e}")
54+
raise
55+
56+
return None

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ classifiers = [
1717
"Intended Audience :: Developers",
1818
"Programming Language :: Python"
1919
]
20-
dependencies = ["fastapi[standard] (>=0.115.6,<0.116.0)"]
20+
dependencies = [
21+
"fastapi[standard] (>=0.115.6,<0.116.0)",
22+
"python-dotenv>=1.0.1",
23+
"redis[hiredis]>=5.2.1",
24+
]
2125

2226
[project.urls]
2327
Repository = "https://github.com/redis-developer/redis-starter-python.git"
@@ -31,6 +35,7 @@ dev = [
3135

3236
[tool.mypy]
3337
strict = true
38+
exclude = ["venv", ".venv"]
3439

3540
[tool.ruff]
3641
target-version = "py38"

uv.lock

Lines changed: 146 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)