@@ -729,62 +729,25 @@ def smart_await[T](coro: Coroutine[Any, Any, T]) -> T:
729729 )
730730
731731
732- def create_sync_wrapper [T ](async_func : Callable [..., Coroutine [Any , Any , T ]]) -> Callable [..., T ]:
732+ def create_sync_wrapper [T ](async_func : Callable [..., Coroutine [Any , Any , T ]], strict : bool = False ) -> Callable [..., T ]:
733733 """
734734 Create a sync wrapper for an async function with context-aware execution.
735735
736736 This wrapper automatically chooses the appropriate async execution method:
737737 - GUI mode: Uses AsyncBridge (Qt event loop integration)
738738 - CLI/TUI mode: Uses asyncio.run() (creates new event loop per call)
739739
740- IMPORTANT - Appropriate Usage:
741- ✅ GUI workers (Qt threads, PySide6 slots)
742- ✅ Testing and benchmarking isolated async functions
743- ✅ One-off operations in sync contexts (initialization, cleanup)
744-
745- ❌ DO NOT USE in production CLI main flow
746- ❌ DO NOT USE when already in async context
747- ❌ DO NOT USE for repeated operations in CLI (inefficient)
748-
749- Best Practices:
750- - Production CLI code should be async-first (use asyncio.run() once at entry point)
751- - See CLASSIC_ScanLogs.py for reference async-first CLI pattern
752- - In CLI, call async methods directly with await instead of using wrappers
753- - Sync wrappers are primarily for GUI thread safety and testing purposes
754-
755- Usage:
756- # Example 1: GUI worker (CORRECT)
757- class CrashLogsScanWorker(QThread):
758- def _perform_scan(self):
759- sync_scan = create_sync_wrapper(async_scan_function)
760- result = sync_scan() # Uses AsyncBridge in GUI mode
761-
762- # Example 2: Testing (CORRECT)
763- def test_async_function():
764- sync_wrapper = create_sync_wrapper(async_function)
765- result = sync_wrapper() # Uses asyncio.run() in CLI mode
766-
767- # Example 3: CLI production (INCORRECT - don't do this)
768- def main():
769- sync_wrapper = create_sync_wrapper(async_function)
770- result = sync_wrapper() # Creates new event loop per call!
771-
772- # Example 4: CLI production (CORRECT - do this instead)
773- async def main():
774- result = await async_function() # Direct async, one event loop
775-
776- if __name__ == "__main__":
777- asyncio.run(main()) # Single event loop at entry point
778-
779740 Args:
780741 async_func: The async function to wrap
742+ strict: If True, raises RuntimeError in CLI/TUI mode instead of falling back
743+ to asyncio.run(). Use this for functions that must only be called
744+ in GUI contexts to prevent performance "footguns".
781745
782746 Returns:
783- A sync wrapper that works in both GUI and CLI modes
747+ A sync wrapper that works in both GUI and CLI modes (unless strict=True)
784748
785- Note:
786- The CLI/TUI mode asyncio.run() fallback is intentional for testing
787- and benchmarking. Production CLI code should not rely on this pattern.
749+ Raises:
750+ RuntimeError: If strict=True and called in CLI/TUI mode.
788751 """
789752 import asyncio
790753
@@ -796,6 +759,15 @@ def wrapper(*args: Any, **kwargs: Any) -> T:
796759 # GUI mode: Use AsyncBridge for Qt event loop integration
797760 bridge = AsyncBridge .get_instance ()
798761 return bridge .run_async (coro )
762+
763+ # Strict mode check - prevent inefficient usage in CLI
764+ if strict :
765+ raise RuntimeError (
766+ f"Strict mode: Cannot use sync wrapper for '{ async_func .__name__ } ' in CLI/TUI mode.\n "
767+ "This function creates a new event loop for every call, which is inefficient.\n "
768+ "Use 'await' and call the async function directly instead."
769+ )
770+
799771 # CLI/TUI mode: Use standard asyncio.run()
800772 return asyncio .run (coro )
801773
0 commit comments