create-caspian-app 0.4.0-rc.3 → 0.4.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.py +64 -3
- package/package.json +1 -1
package/dist/main.py
CHANGED
|
@@ -55,6 +55,12 @@ from casp.runtime_security import (
|
|
|
55
55
|
get_session_secret,
|
|
56
56
|
public_file_response,
|
|
57
57
|
)
|
|
58
|
+
from contextlib import (
|
|
59
|
+
asynccontextmanager,
|
|
60
|
+
AsyncExitStack,
|
|
61
|
+
AbstractAsyncContextManager,
|
|
62
|
+
)
|
|
63
|
+
from collections.abc import Callable
|
|
58
64
|
|
|
59
65
|
load_dotenv()
|
|
60
66
|
cfg = get_config()
|
|
@@ -154,15 +160,70 @@ def setup_auth():
|
|
|
154
160
|
|
|
155
161
|
setup_auth()
|
|
156
162
|
|
|
163
|
+
LifespanFactory = Callable[[FastAPI], AbstractAsyncContextManager[Any]]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_app_lifespans() -> list[LifespanFactory]:
|
|
167
|
+
"""
|
|
168
|
+
Register all application lifespan handlers here.
|
|
169
|
+
|
|
170
|
+
Add a lifespan here when a feature needs startup/shutdown behavior.
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
- Telegram bot/domain workers
|
|
174
|
+
- MCP streamable HTTP server
|
|
175
|
+
- Queue workers
|
|
176
|
+
- Background schedulers
|
|
177
|
+
- Database/cache connection managers
|
|
178
|
+
- WebSocket background services
|
|
179
|
+
|
|
180
|
+
Rule:
|
|
181
|
+
Each item must be a callable that receives the FastAPI app and returns
|
|
182
|
+
an async context manager.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
lifespans.append(app_lifespan)
|
|
186
|
+
|
|
187
|
+
For optional/generated features, guard the lifespan with the related
|
|
188
|
+
config flag or runtime availability check.
|
|
189
|
+
"""
|
|
190
|
+
lifespans: list[LifespanFactory] = []
|
|
191
|
+
|
|
192
|
+
# MCP lifecycle
|
|
193
|
+
# FastMCP needs its lifespan running so the MCP session manager starts.
|
|
194
|
+
if mcp_app is not None:
|
|
195
|
+
lifespans.append(mcp_app.lifespan)
|
|
196
|
+
|
|
197
|
+
return lifespans
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@asynccontextmanager
|
|
201
|
+
async def combined_lifespan(app: FastAPI):
|
|
202
|
+
"""
|
|
203
|
+
Run all registered lifespans using one FastAPI lifespan entrypoint.
|
|
204
|
+
|
|
205
|
+
FastAPI accepts only one `lifespan`, so this function composes multiple
|
|
206
|
+
independent startup/shutdown contexts into a single lifecycle.
|
|
207
|
+
|
|
208
|
+
Startup order:
|
|
209
|
+
- Same order as `get_app_lifespans()`
|
|
210
|
+
|
|
211
|
+
Shutdown order:
|
|
212
|
+
- Reverse order, handled automatically by AsyncExitStack
|
|
213
|
+
"""
|
|
214
|
+
async with AsyncExitStack() as stack:
|
|
215
|
+
for lifespan in get_app_lifespans():
|
|
216
|
+
await stack.enter_async_context(lifespan(app))
|
|
217
|
+
|
|
218
|
+
yield
|
|
219
|
+
|
|
157
220
|
app = FastAPI(
|
|
158
221
|
title=cfg.projectName,
|
|
159
222
|
version=cfg.version,
|
|
160
223
|
docs_url="/docs" if cfg.backendOnly else None,
|
|
161
224
|
redoc_url="/redoc" if cfg.backendOnly else None,
|
|
162
225
|
openapi_url="/openapi.json" if cfg.backendOnly else None,
|
|
163
|
-
|
|
164
|
-
# lifespan; FastAPI must run it or MCP requests fail at runtime.
|
|
165
|
-
lifespan=mcp_app.lifespan if mcp_app is not None else None,
|
|
226
|
+
lifespan=combined_lifespan,
|
|
166
227
|
)
|
|
167
228
|
|
|
168
229
|
|