tastytrade 10.2.2__tar.gz → 10.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {tastytrade-10.2.2 → tastytrade-10.2.3}/PKG-INFO +1 -1
  2. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/__init__.py +1 -1
  3. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/session.py +10 -2
  4. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/streamer.py +39 -37
  5. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_instruments.py +12 -2
  6. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/CONTRIBUTING.md +0 -0
  7. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/FUNDING.yml +0 -0
  8. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/pull_request_template.md +0 -0
  9. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/workflows/python-app.yml +0 -0
  10. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/workflows/python-publish-test.yml +0 -0
  11. {tastytrade-10.2.2 → tastytrade-10.2.3}/.github/workflows/python-publish.yml +0 -0
  12. {tastytrade-10.2.2 → tastytrade-10.2.3}/.gitignore +0 -0
  13. {tastytrade-10.2.2 → tastytrade-10.2.3}/.python-version +0 -0
  14. {tastytrade-10.2.2 → tastytrade-10.2.3}/.readthedocs.yaml +0 -0
  15. {tastytrade-10.2.2 → tastytrade-10.2.3}/LICENSE +0 -0
  16. {tastytrade-10.2.2 → tastytrade-10.2.3}/Makefile +0 -0
  17. {tastytrade-10.2.2 → tastytrade-10.2.3}/README.md +0 -0
  18. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/Makefile +0 -0
  19. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/account-streamer.rst +0 -0
  20. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/accounts.rst +0 -0
  21. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/account.rst +0 -0
  22. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/backtesting.rst +0 -0
  23. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/dxfeed.rst +0 -0
  24. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/instruments.rst +0 -0
  25. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/market-data.rst +0 -0
  26. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/market-sessions.rst +0 -0
  27. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/metrics.rst +0 -0
  28. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/order.rst +0 -0
  29. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/search.rst +0 -0
  30. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/session.rst +0 -0
  31. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/streamer.rst +0 -0
  32. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/utils.rst +0 -0
  33. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/api/watchlists.rst +0 -0
  34. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/backtest.rst +0 -0
  35. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/conf.py +0 -0
  36. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/data-streamer.rst +0 -0
  37. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/img/netliq.png +0 -0
  38. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/index.rst +0 -0
  39. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/installation.rst +0 -0
  40. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/instruments.rst +0 -0
  41. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/make.bat +0 -0
  42. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/market-data.rst +0 -0
  43. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/market-sessions.rst +0 -0
  44. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/orders.rst +0 -0
  45. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/sessions.rst +0 -0
  46. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/sync-async.rst +0 -0
  47. {tastytrade-10.2.2 → tastytrade-10.2.3}/docs/watchlists.rst +0 -0
  48. {tastytrade-10.2.2 → tastytrade-10.2.3}/pyproject.toml +0 -0
  49. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/account.py +0 -0
  50. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/backtest.py +0 -0
  51. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/__init__.py +0 -0
  52. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/candle.py +0 -0
  53. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/event.py +0 -0
  54. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/greeks.py +0 -0
  55. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/profile.py +0 -0
  56. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/quote.py +0 -0
  57. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/summary.py +0 -0
  58. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/theoprice.py +0 -0
  59. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/timeandsale.py +0 -0
  60. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/trade.py +0 -0
  61. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/dxfeed/underlying.py +0 -0
  62. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/instruments.py +0 -0
  63. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/market_data.py +0 -0
  64. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/market_sessions.py +0 -0
  65. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/metrics.py +0 -0
  66. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/order.py +0 -0
  67. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/py.typed +0 -0
  68. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/search.py +0 -0
  69. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/utils.py +0 -0
  70. {tastytrade-10.2.2 → tastytrade-10.2.3}/tastytrade/watchlists.py +0 -0
  71. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/__init__.py +0 -0
  72. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/conftest.py +0 -0
  73. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_account.py +0 -0
  74. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_backtest.py +0 -0
  75. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_dxfeed.py +0 -0
  76. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_market_data.py +0 -0
  77. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_market_sessions.py +0 -0
  78. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_metrics.py +0 -0
  79. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_search.py +0 -0
  80. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_session.py +0 -0
  81. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_streamer.py +0 -0
  82. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_utils.py +0 -0
  83. {tastytrade-10.2.2 → tastytrade-10.2.3}/tests/test_watchlists.py +0 -0
  84. {tastytrade-10.2.2 → tastytrade-10.2.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tastytrade
3
- Version: 10.2.2
3
+ Version: 10.2.3
4
4
  Summary: An unofficial, sync/async SDK for Tastytrade!
5
5
  Project-URL: Homepage, https://github.com/tastyware/tastytrade
6
6
  Project-URL: Documentation, https://tastyworks-api.rtfd.io
@@ -4,7 +4,7 @@ API_URL = "https://api.tastyworks.com"
4
4
  BACKTEST_URL = "https://backtester.vast.tastyworks.com"
5
5
  CERT_URL = "https://api.cert.tastyworks.com"
6
6
  VAST_URL = "https://vast.tastyworks.com"
7
- VERSION = "10.2.2"
7
+ VERSION = "10.2.3"
8
8
 
9
9
  __version__ = VERSION
10
10
  version_str: str = f"tastyware/tastytrade:v{VERSION}"
@@ -578,7 +578,8 @@ class OAuthSession(Session): # pragma: no cover
578
578
  """
579
579
  Refreshes the acccess token using the stored refresh token.
580
580
  """
581
- response = self.sync_client.post(
581
+ request = self.sync_client.build_request(
582
+ "POST",
582
583
  "/oauth/token",
583
584
  json={
584
585
  "grant_type": "refresh_token",
@@ -586,6 +587,9 @@ class OAuthSession(Session): # pragma: no cover
586
587
  "refresh_token": self.refresh_token,
587
588
  },
588
589
  )
590
+ # Don't send the Authorization header for this request
591
+ request.headers.pop("Authorization", None)
592
+ response = self.sync_client.send(request)
589
593
  validate_response(response)
590
594
  data = response.json()
591
595
  # update the relevant tokens
@@ -602,7 +606,8 @@ class OAuthSession(Session): # pragma: no cover
602
606
  """
603
607
  Refreshes the acccess token using the stored refresh token.
604
608
  """
605
- response = await self.async_client.post(
609
+ request = self.async_client.build_request(
610
+ "POST",
606
611
  "/oauth/token",
607
612
  json={
608
613
  "grant_type": "refresh_token",
@@ -610,6 +615,9 @@ class OAuthSession(Session): # pragma: no cover
610
615
  "refresh_token": self.refresh_token,
611
616
  },
612
617
  )
618
+ # Don't send the Authorization header for this request
619
+ request.headers.pop("Authorization", None)
620
+ response = await self.async_client.send(request)
613
621
  validate_response(response)
614
622
  data = response.json()
615
623
  # update the relevant tokens
@@ -278,17 +278,18 @@ class AlertStreamer:
278
278
  """
279
279
  Closes the websocket connection and cancels the pending tasks.
280
280
  """
281
- self._closing = True
282
- self._connect_task.cancel()
283
- tasks = [self._connect_task]
284
- if self._heartbeat_task and not self._heartbeat_task.done():
285
- self._heartbeat_task.cancel()
286
- tasks.append(self._heartbeat_task)
287
- if self._reconnect_task and not self._reconnect_task.done():
288
- self._reconnect_task.cancel()
289
- tasks.append(self._reconnect_task)
290
- await asyncio.gather(*tasks)
291
- await self._websocket.wait_closed() # type: ignore
281
+ if not self._closing: # can only be called once
282
+ self._closing = True
283
+ self._connect_task.cancel()
284
+ tasks = [self._connect_task]
285
+ if self._heartbeat_task and not self._heartbeat_task.done():
286
+ self._heartbeat_task.cancel()
287
+ tasks.append(self._heartbeat_task)
288
+ if self._reconnect_task and not self._reconnect_task.done():
289
+ self._reconnect_task.cancel()
290
+ tasks.append(self._reconnect_task)
291
+ await asyncio.gather(*tasks)
292
+ await self._websocket.wait_closed() # type: ignore
292
293
 
293
294
  async def _connect(self) -> None:
294
295
  """
@@ -316,9 +317,7 @@ class AlertStreamer:
316
317
  logger.error(f"Websocket connection closed with {e}")
317
318
  except asyncio.CancelledError:
318
319
  logger.debug("Websocket interrupted, cancelling main loop.")
319
- if not self._closing:
320
- await self.close()
321
- return
320
+ return await self.close()
322
321
  finally:
323
322
  asyncio.create_task(self.disconnect_fn(self, *self.disconnect_args))
324
323
  logger.debug("Websocket connection closed, retrying...")
@@ -336,9 +335,12 @@ class AlertStreamer:
336
335
  the type of alert to listen for, should be of :any:`AlertType`
337
336
  """
338
337
  cls_str = next(k for k, v in MAP_ALERTS.items() if v == alert_class)
339
- while True:
340
- item = await self._queues[cls_str].get()
341
- yield cast(T, item)
338
+ try:
339
+ while True:
340
+ item = await self._queues[cls_str].get()
341
+ yield cast(T, item)
342
+ except GeneratorExit: # no cleanup needed
343
+ pass
342
344
 
343
345
  async def _map_message(self, type_str: str, data: dict[str, Any]) -> None:
344
346
  """
@@ -515,17 +517,18 @@ class DXLinkStreamer:
515
517
  """
516
518
  Closes the websocket connection and cancels the heartbeat task.
517
519
  """
518
- self._closing = True
519
- self._connect_task.cancel()
520
- tasks = [self._connect_task]
521
- if self._heartbeat_task:
522
- self._heartbeat_task.cancel()
523
- tasks.append(self._heartbeat_task)
524
- if self._reconnect_task is not None and not self._reconnect_task.done():
525
- self._reconnect_task.cancel()
526
- tasks.append(self._reconnect_task)
527
- await asyncio.gather(*tasks)
528
- await self._websocket.wait_closed()
520
+ if not self._closing: # can only be called once
521
+ self._closing = True
522
+ self._connect_task.cancel()
523
+ tasks = [self._connect_task]
524
+ if self._heartbeat_task:
525
+ self._heartbeat_task.cancel()
526
+ tasks.append(self._heartbeat_task)
527
+ if self._reconnect_task is not None and not self._reconnect_task.done():
528
+ self._reconnect_task.cancel()
529
+ tasks.append(self._reconnect_task)
530
+ await asyncio.gather(*tasks)
531
+ await self._websocket.wait_closed()
529
532
 
530
533
  async def _connect(self) -> None:
531
534
  """
@@ -592,8 +595,7 @@ class DXLinkStreamer:
592
595
  pass
593
596
  elif message["type"] == "ERROR":
594
597
  logger.error(f"Fatal streamer error: {message['message']}")
595
- await self.close()
596
- return
598
+ return await self.close()
597
599
  else:
598
600
  logger.error(f"Unknown message: {message}")
599
601
  except ConnectionClosed as e:
@@ -603,13 +605,10 @@ class DXLinkStreamer:
603
605
  "Subscription message too long! Try reducing the number of "
604
606
  "symbols."
605
607
  )
606
- await self.close()
607
- return
608
+ return await self.close()
608
609
  except asyncio.CancelledError:
609
610
  logger.debug("Websocket interrupted, cancelling main loop.")
610
- if not self._closing:
611
- await self.close()
612
- return
611
+ return await self.close()
613
612
  finally:
614
613
  asyncio.create_task(self.disconnect_fn(self, *self.disconnect_args))
615
614
  logger.debug("Websocket connection closed, retrying...")
@@ -645,8 +644,11 @@ class DXLinkStreamer:
645
644
  :param event_class:
646
645
  the type of alert to listen for, should be of :any:`EventType`
647
646
  """
648
- while True:
649
- yield await self._queues[MAP_EVENTS_REVERSE[event_class]].get() # type: ignore
647
+ try:
648
+ while True:
649
+ yield await self._queues[MAP_EVENTS_REVERSE[event_class]].get() # type: ignore
650
+ except GeneratorExit: # no cleanup needed
651
+ pass
650
652
 
651
653
  def get_event_nowait(self, event_class: type[U]) -> Optional[U]:
652
654
  """
@@ -147,7 +147,10 @@ async def test_get_option_chain_async(session: Session):
147
147
  chain = await a_get_option_chain(session, "SPY")
148
148
  assert chain != {}
149
149
  for options in chain.values():
150
- await Option.a_get(session, options[0].symbol)
150
+ single = await Option.a_get(session, options[0].symbol)
151
+ multiple = await Option.a_get(session, [options[0].symbol, options[1].symbol])
152
+ assert isinstance(single, Option)
153
+ assert isinstance(multiple, list)
151
154
  break
152
155
 
153
156
 
@@ -155,7 +158,14 @@ def test_get_option_chain(session: Session):
155
158
  chain = get_option_chain(session, "SPY")
156
159
  assert chain != {}
157
160
  for options in chain.values():
158
- Option.get(session, options[0].symbol)
161
+ single = Option.get(session, options[0].symbol)
162
+ # test setting symbol
163
+ old = single.streamer_symbol
164
+ single._set_streamer_symbol()
165
+ assert single.streamer_symbol == old
166
+ multiple = Option.get(session, [options[0].symbol, options[1].symbol])
167
+ assert isinstance(single, Option)
168
+ assert isinstance(multiple, list)
159
169
  break
160
170
 
161
171
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes