mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2026-11-01 01:24:45 +03:00
feat: implement async scheduler with lifespan management and update README
This commit is contained in:
+3
-13
@@ -22,6 +22,7 @@ from app.middleware.profiler import ProfilingMiddleware
|
||||
from app.redis import get_redis
|
||||
from app.services.auth import AuthBearer
|
||||
from app.services.chat_agent import build_chat_agent
|
||||
from app.services.scheduler import scheduler_lifespan
|
||||
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
||||
|
||||
@@ -45,7 +46,8 @@ async def lifespan(app: FastAPI):
|
||||
# await app.logger.ainfo(
|
||||
# "Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
|
||||
# )
|
||||
yield
|
||||
async with scheduler_lifespan():
|
||||
yield
|
||||
except Exception as e:
|
||||
await app.logger.aerror("Error during app startup", error=repr(e))
|
||||
raise
|
||||
@@ -96,16 +98,4 @@ def create_app() -> FastAPI:
|
||||
|
||||
app = create_app()
|
||||
|
||||
# --- Unused/experimental code and TODOs ---
|
||||
# from apscheduler import AsyncScheduler
|
||||
# from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
|
||||
# from apscheduler.eventbrokers.redis import RedisEventBroker
|
||||
# from app.database import engine
|
||||
# from app.services.scheduler import SchedulerMiddleware
|
||||
# _scheduler_data_store = SQLAlchemyDataStore(engine, schema="scheduler")
|
||||
# _scheduler_event_broker = RedisEventBroker(client_or_url=global_settings.redis_url.unicode_string())
|
||||
# _scheduler_himself = AsyncScheduler(_scheduler_data_store, _scheduler_event_broker)
|
||||
# app.add_middleware(SchedulerMiddleware, scheduler=_scheduler_himself)
|
||||
# TODO: every non-GET method should reset cache
|
||||
# TODO: scheduler tasks needing DB should access connection pool via request
|
||||
# TODO: https://stackoverflow.com/questions/16053364/make-sure-only-one-worker-launches-the-apscheduler-event-in-a-pyramid-web-app-ru
|
||||
|
||||
+22
-26
@@ -1,13 +1,16 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime
|
||||
|
||||
from apscheduler import AsyncScheduler
|
||||
from apscheduler import AsyncScheduler, ConflictPolicy
|
||||
from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
|
||||
from apscheduler.eventbrokers.redis import RedisEventBroker
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from attrs import define
|
||||
from rotoger import get_logger
|
||||
from sqlalchemy import text
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
|
||||
from app.database import AsyncSessionFactory
|
||||
from app.config import settings as global_settings
|
||||
from app.database import AsyncSessionFactory, engine
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
@@ -21,26 +24,19 @@ async def tick():
|
||||
return True
|
||||
|
||||
|
||||
@define
|
||||
class SchedulerMiddleware:
|
||||
app: ASGIApp
|
||||
scheduler: AsyncScheduler
|
||||
@asynccontextmanager
|
||||
async def scheduler_lifespan() -> AsyncIterator[None]:
|
||||
scheduler = AsyncScheduler(
|
||||
SQLAlchemyDataStore(engine, schema="scheduler"),
|
||||
RedisEventBroker(client_or_url=global_settings.redis_url.unicode_string()),
|
||||
)
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
"""
|
||||
Handles the incoming request and schedules tasks if the scope type is 'lifespan'.
|
||||
|
||||
Args:
|
||||
scope (Scope): The ASGI scope dictionary containing request information.
|
||||
receive (Receive): The ASGI receive callable.
|
||||
send (Send): The ASGI send callable.
|
||||
"""
|
||||
if scope["type"] == "lifespan":
|
||||
async with self.scheduler:
|
||||
await self.scheduler.add_schedule(
|
||||
tick, IntervalTrigger(seconds=25), id="tick-sql-25"
|
||||
)
|
||||
await self.scheduler.start_in_background()
|
||||
await self.app(scope, receive, send)
|
||||
else:
|
||||
await self.app(scope, receive, send)
|
||||
async with scheduler:
|
||||
await scheduler.add_schedule(
|
||||
tick,
|
||||
IntervalTrigger(seconds=25),
|
||||
id="tick-sql-25",
|
||||
conflict_policy=ConflictPolicy.replace,
|
||||
)
|
||||
await scheduler.start_in_background()
|
||||
yield
|
||||
|
||||
Reference in New Issue
Block a user