|
1 | 1 | from __future__ import annotations as _annotations |
2 | 2 |
|
| 3 | +import asyncio |
| 4 | +import inspect |
| 5 | +import threading |
3 | 6 | from argparse import Namespace |
4 | 7 | from types import SimpleNamespace |
5 | 8 | from typing import Any, ClassVar, TypeVar |
@@ -446,10 +449,48 @@ class CliApp: |
446 | 449 |
|
447 | 450 | @staticmethod |
448 | 451 | def _run_cli_cmd(model: Any, cli_cmd_method_name: str, is_required: bool) -> Any: |
449 | | - if hasattr(type(model), cli_cmd_method_name): |
450 | | - getattr(type(model), cli_cmd_method_name)(model) |
451 | | - elif is_required: |
452 | | - raise SettingsError(f'Error: {type(model).__name__} class is missing {cli_cmd_method_name} entrypoint') |
| 452 | + command = getattr(type(model), cli_cmd_method_name, None) |
| 453 | + if command is None: |
| 454 | + if is_required: |
| 455 | + raise SettingsError(f'Error: {type(model).__name__} class is missing {cli_cmd_method_name} entrypoint') |
| 456 | + return model |
| 457 | + |
| 458 | + # If the method is asynchronous, we handle its execution based on the current event loop status. |
| 459 | + if inspect.iscoroutinefunction(command): |
| 460 | + # For asynchronous methods, we have two execution scenarios: |
| 461 | + # 1. If no event loop is running in the current thread, run the coroutine directly with asyncio.run(). |
| 462 | + # 2. If an event loop is already running in the current thread, run the coroutine in a separate thread to avoid conflicts. |
| 463 | + try: |
| 464 | + # Check if an event loop is currently running in this thread. |
| 465 | + loop = asyncio.get_running_loop() |
| 466 | + except RuntimeError: |
| 467 | + loop = None |
| 468 | + |
| 469 | + if loop and loop.is_running(): |
| 470 | + # We're in a context with an active event loop (e.g., Jupyter Notebook). |
| 471 | + # Running asyncio.run() here would cause conflicts, so we use a separate thread. |
| 472 | + exception_container = [] |
| 473 | + |
| 474 | + def run_coro() -> None: |
| 475 | + try: |
| 476 | + # Execute the coroutine in a new event loop in this separate thread. |
| 477 | + asyncio.run(command(model)) |
| 478 | + except Exception as e: |
| 479 | + exception_container.append(e) |
| 480 | + |
| 481 | + thread = threading.Thread(target=run_coro) |
| 482 | + thread.start() |
| 483 | + thread.join() |
| 484 | + if exception_container: |
| 485 | + # Propagate exceptions from the separate thread. |
| 486 | + raise exception_container[0] |
| 487 | + else: |
| 488 | + # No event loop is running; safe to run the coroutine directly. |
| 489 | + asyncio.run(command(model)) |
| 490 | + else: |
| 491 | + # For synchronous methods, call them directly. |
| 492 | + command(model) |
| 493 | + |
453 | 494 | return model |
454 | 495 |
|
455 | 496 | @staticmethod |
|
0 commit comments