4545 "tortoise_context" , default = None
4646)
4747
48+ # Optional global fallback context for cross-task access.
49+ # This is used by RegisterTortoise (FastAPI) where asgi-lifespan runs lifespan
50+ # in a background task, but requests/tests run in a different task.
51+ # Disabled by default; enabled via Tortoise.init(_enable_global_fallback=True).
52+ _global_context : TortoiseContext | None = None
53+
4854
4955def get_current_context () -> TortoiseContext | None :
5056 """
5157 Get the currently active TortoiseContext, or None if no context is active.
5258
59+ Checks the contextvar first (for proper isolation), then falls back to
60+ the global context if one was set via _enable_global_fallback.
61+
5362 Returns:
5463 The current TortoiseContext if one is active, None otherwise.
5564 """
56- return _current_context .get ()
65+ ctx = _current_context .get ()
66+ if ctx is not None :
67+ return ctx
68+ return _global_context
69+
70+
71+ def set_global_context (ctx : TortoiseContext ) -> None :
72+ """
73+ Set the global fallback context for cross-task access.
74+
75+ This is used by RegisterTortoise (FastAPI) where asgi-lifespan runs lifespan
76+ in a background task, but requests/tests run in a different task.
77+ The global context allows these cross-task scenarios to work without
78+ explicit context passing.
79+
80+ Args:
81+ ctx: The TortoiseContext to set as global fallback.
82+
83+ Raises:
84+ ConfigurationError: If a global context is already set. Only one global
85+ context can be active at a time. For multiple isolated contexts,
86+ use explicit TortoiseContext() without global fallback.
87+ """
88+ global _global_context
89+ if _global_context is not None :
90+ raise ConfigurationError (
91+ "Global context fallback is already enabled by another Tortoise.init() call. "
92+ "Only one global context can be active at a time. "
93+ "Use explicit TortoiseContext() for multiple isolated contexts, "
94+ "or set _enable_global_fallback=False for secondary apps."
95+ )
96+ _global_context = ctx
5797
5898
5999def require_context () -> TortoiseContext :
@@ -230,6 +270,7 @@ async def init(
230270 routers : list [str | type ] | None = None ,
231271 table_name_generator : Callable [[type [Model ]], str ] | None = None ,
232272 init_connections : bool = True ,
273+ _enable_global_fallback : bool = False ,
233274 ) -> None :
234275 """
235276 Initialize this context with database configuration.
@@ -255,6 +296,8 @@ async def init(
255296 table_name_generator: Optional callable to generate table names.
256297 init_connections: If False, skips initializing connection clients while still
257298 loading apps and validating connection names against the config.
299+ _enable_global_fallback: If True, sets this context as the global fallback
300+ for cross-task access (e.g., asgi-lifespan scenarios). Default is False.
258301
259302 Raises:
260303 ConfigurationError: If configuration is invalid or incomplete.
@@ -334,6 +377,10 @@ async def init(
334377
335378 self ._inited = True
336379
380+ # Set global fallback for cross-task access if enabled
381+ if _enable_global_fallback :
382+ set_global_context (self )
383+
337384 def _init_timezone (self , use_tz : bool , timezone : str ) -> None :
338385 """Initialize timezone settings for this context."""
339386 self ._use_tz = use_tz
@@ -478,9 +525,13 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
478525 """
479526 Exit the async context manager, close connections, and restore previous context.
480527 """
528+ global _global_context
481529 await self .close_connections ()
482530 self ._apps = None
483531 self ._inited = False
532+ # Clear global context if this context was set as the global fallback
533+ if _global_context is self :
534+ _global_context = None
484535 self .__exit__ (exc_type , exc_val , exc_tb )
485536
486537
@@ -558,5 +609,6 @@ async def test_create_user(db):
558609 "TortoiseContext" ,
559610 "get_current_context" ,
560611 "require_context" ,
612+ "set_global_context" ,
561613 "tortoise_test_context" ,
562614]
0 commit comments