Skip to content

Commit 0695ecb

Browse files
committed
Support asynchronous command execution in CliApp.run
1 parent 65929cd commit 0695ecb

File tree

1 file changed

+45
-4
lines changed

1 file changed

+45
-4
lines changed

pydantic_settings/main.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from __future__ import annotations as _annotations
22

3+
import asyncio
4+
import inspect
5+
import threading
36
from argparse import Namespace
47
from types import SimpleNamespace
58
from typing import Any, ClassVar, TypeVar
@@ -446,10 +449,48 @@ class CliApp:
446449

447450
@staticmethod
448451
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+
453494
return model
454495

455496
@staticmethod

0 commit comments

Comments
 (0)