@@ -62,7 +62,8 @@ def __init__(
62
62
self ._timeout = timeout
63
63
self ._async_client : Optional [HummingbotAPIClient ] = None
64
64
self ._loop : Optional [asyncio .AbstractEventLoop ] = None
65
-
65
+ self ._created_loop : bool = False
66
+
66
67
# Type hints for dynamically created attributes
67
68
if TYPE_CHECKING :
68
69
self .accounts : AccountsRouter
@@ -76,12 +77,19 @@ def __init__(
76
77
self .portfolio : PortfolioRouter
77
78
self .scripts : ScriptsRouter
78
79
self .trading : TradingRouter
79
-
80
+
80
81
def __enter__ (self ) -> 'SyncHummingbotAPIClient' :
81
82
"""Enter context manager and initialize the async client."""
82
- self ._loop = asyncio .new_event_loop ()
83
- asyncio .set_event_loop (self ._loop )
84
-
83
+ # Check if there's already a running event loop
84
+ try :
85
+ self ._loop = asyncio .get_running_loop ()
86
+ self ._created_loop = False
87
+ except RuntimeError :
88
+ # No running loop, create a new one
89
+ self ._loop = asyncio .new_event_loop ()
90
+ asyncio .set_event_loop (self ._loop )
91
+ self ._created_loop = True
92
+
85
93
# Create and initialize the async client
86
94
import aiohttp
87
95
timeout_obj = aiohttp .ClientTimeout (total = self ._timeout ) if self ._timeout else None
@@ -91,20 +99,53 @@ def __enter__(self) -> 'SyncHummingbotAPIClient':
91
99
self ._password ,
92
100
timeout = timeout_obj
93
101
)
94
- self ._loop .run_until_complete (self ._async_client .init ())
95
-
102
+
103
+ # Initialize based on whether we created the loop
104
+ if self ._created_loop :
105
+ self ._loop .run_until_complete (self ._async_client .init ())
106
+ else :
107
+ # For existing loop, schedule coroutine as a task
108
+ import concurrent .futures
109
+ future = concurrent .futures .Future ()
110
+
111
+ async def init_wrapper ():
112
+ try :
113
+ await self ._async_client .init ()
114
+ future .set_result (None )
115
+ except Exception as e :
116
+ future .set_exception (e )
117
+
118
+ asyncio .run_coroutine_threadsafe (init_wrapper (), self ._loop )
119
+ future .result () # Wait for completion
120
+
96
121
# Dynamically create sync wrappers for all routers
97
122
self ._wrap_routers ()
98
-
123
+
99
124
return self
100
-
125
+
101
126
def __exit__ (self , exc_type , exc_val , exc_tb ):
102
127
"""Exit context manager and cleanup resources."""
103
128
if self ._async_client :
104
- self ._loop .run_until_complete (self ._async_client .close ())
105
- if self ._loop :
129
+ if self ._created_loop :
130
+ self ._loop .run_until_complete (self ._async_client .close ())
131
+ else :
132
+ # For existing loop, use run_coroutine_threadsafe
133
+ import concurrent .futures
134
+ future = concurrent .futures .Future ()
135
+
136
+ async def close_wrapper ():
137
+ try :
138
+ await self ._async_client .close ()
139
+ future .set_result (None )
140
+ except Exception as e :
141
+ future .set_exception (e )
142
+
143
+ asyncio .run_coroutine_threadsafe (close_wrapper (), self ._loop )
144
+ future .result () # Wait for completion
145
+
146
+ if self ._created_loop and self ._loop :
106
147
self ._loop .close ()
107
-
148
+
108
149
def _wrap_routers (self ):
109
150
"""Dynamically wrap all router methods to be synchronous."""
110
151
# List of router attributes on the async client
@@ -113,28 +154,45 @@ def _wrap_routers(self):
113
154
'connectors' , 'controllers' , 'docker' , 'market_data' ,
114
155
'portfolio' , 'scripts' , 'trading'
115
156
]
116
-
157
+
117
158
for router_name in router_attrs :
118
159
if hasattr (self ._async_client , router_name ):
119
160
async_router = getattr (self ._async_client , router_name )
120
- sync_router = SyncRouterWrapper (async_router , self ._loop )
161
+ sync_router = SyncRouterWrapper (async_router , self ._loop , self . _created_loop )
121
162
setattr (self , router_name , sync_router )
122
163
123
164
124
165
class SyncRouterWrapper :
125
166
"""Wrapper that converts async router methods to sync."""
126
-
127
- def __init__ (self , async_router : Any , loop : asyncio .AbstractEventLoop ):
167
+
168
+ def __init__ (self , async_router : Any , loop : asyncio .AbstractEventLoop , created_loop : bool ):
128
169
self ._async_router = async_router
129
170
self ._loop = loop
130
-
171
+ self ._created_loop = created_loop
172
+
131
173
def __getattr__ (self , name : str ) -> Any :
132
174
"""Dynamically wrap async methods to be synchronous."""
133
175
attr = getattr (self ._async_router , name )
134
-
176
+
135
177
if asyncio .iscoroutinefunction (attr ):
136
178
def sync_method (* args , ** kwargs ):
137
- return self ._loop .run_until_complete (attr (* args , ** kwargs ))
179
+ if self ._created_loop :
180
+ # We created the loop, so we can use run_until_complete
181
+ return self ._loop .run_until_complete (attr (* args , ** kwargs ))
182
+ else :
183
+ # Using existing loop, must use run_coroutine_threadsafe
184
+ import concurrent .futures
185
+ future = concurrent .futures .Future ()
186
+
187
+ async def wrapper ():
188
+ try :
189
+ result = await attr (* args , ** kwargs )
190
+ future .set_result (result )
191
+ except Exception as e :
192
+ future .set_exception (e )
193
+
194
+ asyncio .run_coroutine_threadsafe (wrapper (), self ._loop )
195
+ return future .result ()
138
196
return sync_method
139
197
140
198
return attr
0 commit comments