Skip to content

Commit 12970b6

Browse files
authored
Python 3.10 (#561)
* upgrade websockets version to 10.1 * use updated asyncio api methods * flake8 format * python 310 circle ci
1 parent 4e65915 commit 12970b6

File tree

6 files changed

+87
-51
lines changed

6 files changed

+87
-51
lines changed

.circleci/config.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
name: run tests
9797
command: |
9898
pip install pandas==1.3.4 numpy==1.21.4
99-
python setup.py install
99+
pip install .
100100
pip install flake8 && flake8 alpaca_trade_api tests
101101
python setup.py test
102102
@@ -118,7 +118,7 @@ jobs:
118118
- run:
119119
name: make sure install works
120120
command: |
121-
python setup.py install
121+
pip install .
122122
- run:
123123
name: view installed packages
124124
command: |
@@ -133,11 +133,30 @@ jobs:
133133
- run:
134134
name: make sure install works
135135
command: |
136-
python setup.py install
136+
pip install .
137+
- run:
138+
name: view installed packages
139+
command: |
140+
pip freeze
141+
142+
build-test-python310:
143+
docker:
144+
- image: python:3.10
145+
working_directory: ~/repo
146+
steps:
147+
- checkout
148+
- run:
149+
name: make sure install works
150+
command: |
151+
pip install .
137152
- run:
138153
name: view installed packages
139154
command: |
140155
pip freeze
156+
- run:
157+
name: unit test
158+
command: |
159+
python setup.py test
141160
142161
workflows:
143162
version: 2
@@ -148,3 +167,4 @@ workflows:
148167
- build-test-python37
149168
- build-python38
150169
- build-python39
170+
- build-test-python310

alpaca_trade_api/stream.py

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self,
5151
self._secret_key = secret_key
5252
self._ws = None
5353
self._running = False
54+
self._loop = None
5455
self._raw_data = raw_data
5556
self._stop_stream_queue = queue.Queue()
5657
self._handlers = {
@@ -179,7 +180,9 @@ def _subscribe(self, handler, symbols, handlers):
179180
for symbol in symbols:
180181
handlers[symbol] = handler
181182
if self._running:
182-
asyncio.get_event_loop().run_until_complete(self._subscribe_all())
183+
asyncio.run_coroutine_threadsafe(
184+
self._subscribe_all(), self._loop
185+
).result()
183186

184187
async def _subscribe_all(self):
185188
if any(
@@ -210,6 +213,7 @@ async def _unsubscribe(self,
210213
}))
211214

212215
async def _run_forever(self):
216+
self._loop = asyncio.get_running_loop()
213217
# do not start the websocket connection until we subscribe to something
214218
while not any(
215219
v for k, v in self._handlers.items()
@@ -262,32 +266,42 @@ def subscribe_daily_bars(self, handler, *symbols):
262266

263267
def unsubscribe_trades(self, *symbols):
264268
if self._running:
265-
asyncio.get_event_loop().run_until_complete(
266-
self._unsubscribe(trades=symbols))
269+
asyncio.run_coroutine_threadsafe(
270+
self._unsubscribe(trades=symbols),
271+
self._loop).result()
267272
for symbol in symbols:
268273
del self._handlers['trades'][symbol]
269274

270275
def unsubscribe_quotes(self, *symbols):
271276
if self._running:
272-
asyncio.get_event_loop().run_until_complete(
273-
self._unsubscribe(quotes=symbols))
277+
asyncio.run_coroutine_threadsafe(
278+
self._unsubscribe(quotes=symbols),
279+
self._loop).result()
274280
for symbol in symbols:
275281
del self._handlers['quotes'][symbol]
276282

277283
def unsubscribe_bars(self, *symbols):
278284
if self._running:
279-
asyncio.get_event_loop().run_until_complete(
280-
self._unsubscribe(bars=symbols))
285+
asyncio.run_coroutine_threadsafe(
286+
self._unsubscribe(bars=symbols),
287+
self._loop).result()
281288
for symbol in symbols:
282289
del self._handlers['bars'][symbol]
283290

284291
def unsubscribe_daily_bars(self, *symbols):
285292
if self._running:
286-
asyncio.get_event_loop().run_until_complete(
287-
self._unsubscribe(daily_bars=symbols))
293+
asyncio.run_coroutine_threadsafe(
294+
self._unsubscribe(daily_bars=symbols),
295+
self._loop).result()
288296
for symbol in symbols:
289297
del self._handlers['dailyBars'][symbol]
290298

299+
def stop(self):
300+
if self._loop.is_running():
301+
asyncio.run_coroutine_threadsafe(
302+
self.stop_ws(),
303+
self._loop).result()
304+
291305

292306
class DataStream(_DataStream):
293307
def __init__(self,
@@ -386,15 +400,17 @@ def subscribe_lulds(self, handler, *symbols):
386400

387401
def unsubscribe_statuses(self, *symbols):
388402
if self._running:
389-
asyncio.get_event_loop().run_until_complete(
390-
self._unsubscribe(statuses=symbols))
403+
asyncio.run_coroutine_threadsafe(
404+
self._unsubscribe(statuses=symbols),
405+
self._loop).result()
391406
for symbol in symbols:
392407
del self._handlers['statuses'][symbol]
393408

394409
def unsubscribe_lulds(self, *symbols):
395410
if self._running:
396-
asyncio.get_event_loop().run_until_complete(
397-
self._unsubscribe(lulds=symbols))
411+
asyncio.run_coroutine_threadsafe(
412+
self._unsubscribe(lulds=symbols),
413+
self._loop).result()
398414
for symbol in symbols:
399415
del self._handlers['lulds'][symbol]
400416

@@ -484,8 +500,9 @@ def subscribe_news(self, handler, *symbols):
484500

485501
def unsubscribe_news(self, *symbols):
486502
if self._running:
487-
asyncio.get_event_loop().run_until_complete(
488-
self._unsubscribe(news=symbols))
503+
asyncio.run_coroutine_threadsafe(
504+
self._unsubscribe(news=symbols),
505+
self._loop).result()
489506
for symbol in symbols:
490507
del self._handlers['news'][symbol]
491508

@@ -503,6 +520,7 @@ def __init__(self,
503520
self._trade_updates_handler = None
504521
self._ws = None
505522
self._running = False
523+
self._loop = None
506524
self._raw_data = raw_data
507525
self._stop_stream_queue = queue.Queue()
508526
self._should_run = True
@@ -550,8 +568,9 @@ def subscribe_trade_updates(self, handler):
550568
_ensure_coroutine(handler)
551569
self._trade_updates_handler = handler
552570
if self._running:
553-
asyncio.get_event_loop().run_until_complete(
554-
self._subscribe_trade_updates())
571+
asyncio.run_coroutine_threadsafe(
572+
self._subscribe_trade_updates(),
573+
self._loop).result()
555574

556575
async def _start_ws(self):
557576
await self._connect()
@@ -577,6 +596,7 @@ async def _consume(self):
577596
pass
578597

579598
async def _run_forever(self):
599+
self._loop = asyncio.get_running_loop()
580600
# do not start the websocket connection until we subscribe to something
581601
while not self._trade_updates_handler:
582602
if not self._stop_stream_queue.empty():
@@ -590,7 +610,7 @@ async def _run_forever(self):
590610
try:
591611
if not self._should_run:
592612
log.info("Trading stream stopped")
593-
break
613+
return
594614
if not self._running:
595615
log.info("starting trading websocket connection")
596616
await self._start_ws()
@@ -618,6 +638,12 @@ async def stop_ws(self):
618638
if self._stop_stream_queue.empty():
619639
self._stop_stream_queue.put_nowait({"should_stop": True})
620640

641+
def stop(self):
642+
if self._loop.is_running():
643+
asyncio.run_coroutine_threadsafe(
644+
self.stop_ws(),
645+
self._loop).result()
646+
621647

622648
class Stream:
623649
def __init__(self,
@@ -630,25 +656,25 @@ def __init__(self,
630656
crypto_exchanges: Optional[List[str]] = None):
631657
self._key_id, self._secret_key, _ = get_credentials(key_id, secret_key)
632658
self._base_url = base_url or get_base_url()
633-
self._data_steam_url = data_stream_url or get_data_stream_url()
659+
self._data_stream_url = data_stream_url or get_data_stream_url()
634660

635661
self._trading_ws = TradingStream(self._key_id,
636662
self._secret_key,
637663
self._base_url,
638664
raw_data)
639665
self._data_ws = DataStream(self._key_id,
640666
self._secret_key,
641-
self._data_steam_url,
667+
self._data_stream_url,
642668
raw_data,
643669
data_feed.lower())
644670
self._crypto_ws = CryptoDataStream(self._key_id,
645671
self._secret_key,
646-
self._data_steam_url,
672+
self._data_stream_url,
647673
raw_data,
648674
crypto_exchanges)
649675
self._news_ws = NewsDataStream(self._key_id,
650676
self._secret_key,
651-
self._data_steam_url,
677+
self._data_stream_url,
652678
raw_data)
653679

654680
def subscribe_trade_updates(self, handler):
@@ -836,9 +862,8 @@ async def _run_forever(self):
836862
self._news_ws._run_forever())
837863

838864
def run(self):
839-
loop = asyncio.get_event_loop()
840865
try:
841-
loop.run_until_complete(self._run_forever())
866+
asyncio.run(self._run_forever())
842867
except KeyboardInterrupt:
843868
print('keyboard interrupt, bye')
844869
pass
@@ -859,6 +884,16 @@ async def stop_ws(self):
859884
if self._news_ws:
860885
await self._news_ws.stop_ws()
861886

887+
def stop(self):
888+
if self._trading_ws:
889+
self._trading_ws.stop()
890+
if self._data_ws:
891+
self._data_ws.stop()
892+
if self._crypto_ws:
893+
self._crypto_ws.stop()
894+
if self._news_ws:
895+
self._news_ws.stop()
896+
862897
def is_open(self):
863898
"""
864899
Checks if either of the websockets is open

examples/historic_async.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ async def main(symbols):
106106
base_url=URL(base_url))
107107

108108
start_time = time.time()
109-
loop = asyncio.get_event_loop()
110109
symbols = [el.symbol for el in api.list_assets(status='active')]
111110
symbols = symbols[:200]
112-
loop.run_until_complete(main(symbols))
111+
asyncio.run(main(symbols))
113112
print(f"took {time.time() - start_time} sec")

examples/websockets/dynamic_subscription_example.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,6 @@ async def print_bar(bar):
2828

2929

3030
def consumer_thread():
31-
try:
32-
# make sure we have an event loop, if not create a new one
33-
loop = asyncio.get_event_loop()
34-
loop.set_debug(True)
35-
except RuntimeError:
36-
asyncio.set_event_loop(asyncio.new_event_loop())
37-
3831
global conn
3932
conn = Stream(ALPACA_API_KEY,
4033
ALPACA_SECRET_KEY,
@@ -50,9 +43,6 @@ def consumer_thread():
5043
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
5144
level=logging.INFO)
5245
threading.Thread(target=consumer_thread).start()
53-
54-
loop = asyncio.get_event_loop()
55-
5646
time.sleep(5) # give the initial connection time to be established
5747
subscriptions = {"BABA": print_quote,
5848
"AAPL": print_quote,

examples/websockets/streamconn_on_and_off.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ async def print_bar(bar):
2727

2828

2929
def consumer_thread():
30-
try:
31-
# make sure we have an event loop, if not create a new one
32-
loop = asyncio.get_event_loop()
33-
loop.set_debug(True)
34-
except RuntimeError:
35-
asyncio.set_event_loop(asyncio.new_event_loop())
36-
3730
global conn
3831
conn = Stream(ALPACA_API_KEY,
3932
ALPACA_SECRET_KEY,
@@ -48,18 +41,17 @@ def consumer_thread():
4841
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
4942
level=logging.INFO)
5043

51-
loop = asyncio.get_event_loop()
5244
pool = ThreadPoolExecutor(1)
5345

5446
while 1:
5547
try:
5648
pool.submit(consumer_thread)
5749
time.sleep(20)
58-
loop.run_until_complete(conn.stop_ws())
50+
conn.stop()
5951
time.sleep(20)
6052
except KeyboardInterrupt:
6153
print("Interrupted execution by user")
62-
loop.run_until_complete(conn.stop_ws())
54+
conn.stop()
6355
exit(0)
6456
except Exception as e:
6557
print("You goe an exception: {} during execution. continue "

requirements/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ numpy>=1.11.1 # pyup: ignore - allow all versions above this
33
requests>2,<3
44
urllib3>1.24,<2
55
websocket-client>=0.56.0,<2
6-
websockets>=8.0,<10
6+
websockets>=8.0,<11
77
msgpack==1.0.2
88
aiohttp==3.7.4
9-
PyYAML==5.4.1
9+
PyYAML==6.0
1010
deprecation==2.1.0

0 commit comments

Comments
 (0)