litestar: Custom Middleware with a kwarg fails to load

Describe the bug I am trying to use APScheduler with Starlite. APScheduler is a background task scheduling package. I am currently using their latest alpha version 4.0.0a2. They have a working example for Starlette (copied below), but when I adapt the code for Starlite (below), it seems the middleware is never called.

When I try to debug, I can see the SchedulerMiddleware gets initialized, but the __call__ dunder doesn’t seem to get called, I never get to step through the code.

Based on the Starlite middleware docs, I believe I’m calling this middleware correctly with the kwarg.

Have I don’t something wrong, or is this a bug?

To Reproduce The code below should run as-is. Only requirements are Starlite=1.32.0, Uvicorn and APScheduler=4.0.0a2.

Expected result: print or log statement every second from the tick() function. Actual result is nothing.

from __future__ import annotations

import logging
from datetime import datetime

import uvicorn
from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore
from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker
from apscheduler.schedulers.async_ import AsyncScheduler
from apscheduler.triggers.interval import IntervalTrigger
from sqlalchemy.ext.asyncio import create_async_engine
from starlite import DefineMiddleware, Request, Starlite, State, get
from starlite.middleware.base import MiddlewareProtocol
from starlite.types import ASGIApp, Receive, Scope, Send

logger = logging.getLogger(__name__)


def tick():
    print("Hello, the time is", datetime.now())
    logger.info("Hello, the time is", datetime.now())


class SchedulerMiddleware(MiddlewareProtocol):
    def __init__(
        self,
        app: ASGIApp,
        scheduler: AsyncScheduler,
    ) -> None:
        super().__init__(app)
        self.app = app
        self.scheduler = scheduler

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] == "lifespan":
            async with self.scheduler:
                await self.scheduler.add_schedule(
                    tick, IntervalTrigger(seconds=1), id="tick"
                )
                await self.scheduler.start_in_background()
                await self.app(scope, receive, send)
        else:
            await self.app(scope, receive, send)


@get("/")
async def root(request: Request, state: State) -> str:
    return "Hello, world!"


engine = create_async_engine("postgresql+asyncpg://postgres:secret@localhost/testdb")
data_store = AsyncSQLAlchemyDataStore(engine)
event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine)
scheduler = AsyncScheduler()
middleware = [DefineMiddleware(SchedulerMiddleware, scheduler=scheduler)]

app = Starlite(route_handlers=[root], middleware=middleware)

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

Additional context Here is an example using Starlette which works:

from __future__ import annotations

from datetime import datetime

from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore
from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker
from apscheduler.schedulers.async_ import AsyncScheduler
from apscheduler.triggers.interval import IntervalTrigger
from sqlalchemy.ext.asyncio import create_async_engine
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import PlainTextResponse, Response
from starlette.routing import Route
from starlette.types import ASGIApp, Receive, Scope, Send


def tick():
    print("Hello, the time is", datetime.now())


class SchedulerMiddleware:
    def __init__(
        self,
        app: ASGIApp,
        scheduler: AsyncScheduler,
    ) -> None:
        self.app = app
        self.scheduler = scheduler

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] == "lifespan":
            async with self.scheduler:
                await self.scheduler.add_schedule(
                    tick, IntervalTrigger(seconds=1), id="tick"
                )
                await self.scheduler.start_in_background()
                await self.app(scope, receive, send)
        else:
            await self.app(scope, receive, send)


async def root(request: Request) -> Response:
    return PlainTextResponse("Hello, world!")


engine = create_async_engine("postgresql+asyncpg://postgres:secret@localhost/testdb")
data_store = AsyncSQLAlchemyDataStore(engine)
event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine)
scheduler = AsyncScheduler()
routes = [Route("/", root)]
middleware = [Middleware(SchedulerMiddleware, scheduler=scheduler)]
app = Starlette(routes=routes, middleware=middleware)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (8 by maintainers)

Most upvoted comments

We currently don’t give Middleware access to the asgi LifeSpan events. That’s what’s going on there. Scaffolding the scheduler should happen in on_startup.