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.
Files changed (2) hide show
  1. package/dist/main.py +64 -3
  2. 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
- # FastMCP's streamable-HTTP transport starts its session manager in its
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "0.4.0-rc.3",
3
+ "version": "0.4.0-rc.4",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",