Skip to content

Commit 804711d

Browse files
committed
Type annotation tweaks, temporary httpx-ws bugfix
- Use more permissive Iterable type in API calls where any iterable works. Previously a list was required. - Add temporary fix for frankie567/httpx-ws#107 until frankie567/httpx-ws#129 is merged. - Add docs on using anyio BlockingPortals to use the SDK in a sync context. - Fix broken part of streamer proxy test
1 parent b13d11e commit 804711d

File tree

12 files changed

+120
-65
lines changed

12 files changed

+120
-65
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ A typed, async SDK for Tastytrade built on their public API. This will allow you
4444
market-data
4545
market-sessions
4646
watchlists
47+
sync
4748

4849
.. toctree::
4950
:maxdepth: 2

docs/sync.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Sync Contexts
2+
=============
3+
4+
Since v12.0.0, the SDK is async-only. However, that doesn't mean there is no way make sync requests as before--you'll just have to jump through a few hoops to do so.
5+
6+
Blocking portals
7+
----------------
8+
9+
``anyio`` provides a useful tool for stringing together async calls in a sync context called a blocking portal, which runs an event loop in a dedicated thread:
10+
11+
.. code-block:: python
12+
13+
from functools import partial
14+
from anyio.from_thread import start_blocking_portal
15+
from tastytrade import Account, Session
16+
17+
sesh = Session("secret", "refresh")
18+
with start_blocking_portal() as portal:
19+
acc = portal.call(Account.get, sesh, "5WX01234")
20+
print(
21+
portal.call(
22+
# we use partial since anyio functions don't support keyword arguments
23+
partial(acc.get_positions, sesh, underlying_symbols=["IBIT", "AVDV"])
24+
)
25+
)
26+
27+
This allows you to weave together sync and async calls seamlessly in a sync context. It even works with streamers, which wasn't possible before:
28+
29+
.. code-block:: python
30+
31+
from tastytrade import DXLinkStreamer
32+
from tastytrade.dxfeed import Quote
33+
34+
with start_blocking_portal() as portal:
35+
streamer = DXLinkStreamer(sesh)
36+
with portal.wrap_async_context_manager(streamer.__asynccontextmanager__()):
37+
portal.call(streamer.subscribe, Quote, ["SPY"])
38+
print(portal.call(streamer.get_event, Quote))
39+
40+
You can read more about this functionality in the `anyio docs <https://anyio.readthedocs.io/en/stable/threads.html#running-code-from-threads-using-blocking-portals>`_.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ authors = [
4242
dependencies = [
4343
"anyio>=4.12.1",
4444
"httpx>=0.28.1",
45-
"httpx-ws>=0.7.2",
45+
"httpx-ws>=0.8.2",
4646
"pandas-market-calendars>=5.1.1",
4747
"pydantic>=2.12.0",
4848
]

tastytrade/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
API_URL = "https://api.tastyworks.com"
44
API_VERSION = "20251101"
55
CERT_URL = "https://api.cert.tastyworks.com"
6-
VERSION = "12.0.1"
6+
VERSION = "12.0.2"
77

88
__version__ = VERSION
99
version_str: str = f"tastyware/tastytrade:v{VERSION}"

tastytrade/account.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import date, datetime
22
from decimal import Decimal
3-
from typing import Any, Literal, Self, cast, overload
3+
from typing import Any, Iterable, Literal, Self, cast, overload
44

55
from pydantic import BaseModel, ConfigDict, model_validator
66

@@ -553,12 +553,12 @@ async def get_balance_snapshots(
553553
async def get_positions(
554554
self,
555555
session: Session,
556-
underlying_symbols: list[str] | None = None,
556+
underlying_symbols: Iterable[str] | None = None,
557557
symbol: str | None = None,
558558
instrument_type: InstrumentType | None = None,
559559
include_closed: bool | None = None,
560560
underlying_product_code: str | None = None,
561-
partition_keys: list[str] | None = None,
561+
partition_keys: Iterable[str] | None = None,
562562
net_positions: bool | None = None,
563563
include_marks: bool | None = None,
564564
) -> list[CurrentPosition]:
@@ -602,8 +602,8 @@ async def get_history(
602602
page_offset: int | None = 0,
603603
sort: Literal["Asc", "Desc"] = "Desc",
604604
type: str | None = None,
605-
types: list[str] | None = None,
606-
sub_types: list[str] | None = None,
605+
types: Iterable[str] | None = None,
606+
sub_types: Iterable[str] | None = None,
607607
start_date: date | None = None,
608608
end_date: date | None = None,
609609
instrument_type: InstrumentType | None = None,
@@ -810,7 +810,7 @@ async def get_order_history(
810810
start_date: date | None = None,
811811
end_date: date | None = None,
812812
underlying_symbol: str | None = None,
813-
statuses: list[OrderStatus] | None = None,
813+
statuses: Iterable[OrderStatus] | None = None,
814814
futures_symbol: str | None = None,
815815
underlying_instrument_type: InstrumentType | None = None,
816816
sort: Literal["Asc", "Desc"] | None = None,

tastytrade/instruments.py

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import date, datetime
44
from decimal import Decimal
55
from enum import Enum
6-
from typing import Any, Self, overload
6+
from typing import Any, Iterable, Self, overload
77

88
from pydantic import field_validator, model_validator
99

@@ -207,19 +207,19 @@ class Cryptocurrency(TradeableTastytradeData):
207207

208208
@overload
209209
@classmethod
210-
async def get(
211-
cls, session: Session, symbols: list[str] | None = None
212-
) -> list[Self]: ...
210+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
213211

214212
@overload
215213
@classmethod
216-
async def get(cls, session: Session, symbols: str) -> Self: ...
214+
async def get(
215+
cls, session: Session, symbols: Iterable[str] | None = None
216+
) -> list[Self]: ...
217217

218218
@classmethod
219219
async def get(
220220
cls,
221221
session: Session,
222-
symbols: str | list[str] | None = None,
222+
symbols: str | Iterable[str] | None = None,
223223
) -> Self | list[Self]:
224224
"""
225225
Returns a list of cryptocurrency objects from the given symbols,
@@ -290,12 +290,16 @@ async def get_active_equities(
290290
}
291291
return await session._paginate(cls, "/instruments/equities/active", params)
292292

293+
@overload
294+
@classmethod
295+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
296+
293297
@overload
294298
@classmethod
295299
async def get(
296300
cls,
297301
session: Session,
298-
symbols: list[str],
302+
symbols: Iterable[str],
299303
*,
300304
per_page: int = 250,
301305
page_offset: int | None = 0,
@@ -304,15 +308,11 @@ async def get(
304308
is_etf: bool | None = None,
305309
) -> list[Self]: ...
306310

307-
@overload
308-
@classmethod
309-
async def get(cls, session: Session, symbols: str) -> Self: ...
310-
311311
@classmethod
312312
async def get(
313313
cls,
314314
session: Session,
315-
symbols: str | list[str],
315+
symbols: str | Iterable[str],
316316
per_page: int = 250,
317317
page_offset: int | None = 0,
318318
lendability: str | None = None,
@@ -382,28 +382,28 @@ def set_streamer_symbol(self) -> Self:
382382
self._set_streamer_symbol()
383383
return self
384384

385+
@overload
386+
@classmethod
387+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
388+
385389
@overload
386390
@classmethod
387391
async def get(
388392
cls,
389393
session: Session,
390-
symbols: list[str],
394+
symbols: Iterable[str],
391395
*,
392396
active: bool | None = None,
393397
per_page: int = 250,
394398
page_offset: int | None = 0,
395399
with_expired: bool | None = None,
396400
) -> list[Self]: ...
397401

398-
@overload
399-
@classmethod
400-
async def get(cls, session: Session, symbols: str) -> Self: ...
401-
402402
@classmethod
403403
async def get(
404404
cls,
405405
session: Session,
406-
symbols: str | list[str],
406+
symbols: str | Iterable[str],
407407
*,
408408
active: bool | None = None,
409409
per_page: int = 250,
@@ -636,27 +636,27 @@ class Future(TradeableTastytradeData):
636636
option_tick_sizes: list[TickSize] | None = None
637637
spread_tick_sizes: list[TickSize] | None = None
638638

639+
@overload
640+
@classmethod
641+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
642+
639643
@overload
640644
@classmethod
641645
async def get(
642646
cls,
643647
session: Session,
644-
symbols: list[str] | None = None,
648+
symbols: Iterable[str] | None = None,
645649
*,
646650
product_codes: list[str] | None = None,
647651
per_page: int = 250,
648652
page_offset: int | None = 0,
649653
) -> list[Self]: ...
650654

651-
@overload
652-
@classmethod
653-
async def get(cls, session: Session, symbols: str) -> Self: ...
654-
655655
@classmethod
656656
async def get(
657657
cls,
658658
session: Session,
659-
symbols: str | list[str] | None = None,
659+
symbols: str | Iterable[str] | None = None,
660660
*,
661661
product_codes: list[str] | None = None,
662662
per_page: int = 250,
@@ -796,12 +796,16 @@ def parse_date_with_utc(cls, value: Any) -> Any:
796796
return value.split(" ")[0]
797797
return value
798798

799+
@overload
800+
@classmethod
801+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
802+
799803
@overload
800804
@classmethod
801805
async def get(
802806
cls,
803807
session: Session,
804-
symbols: list[str],
808+
symbols: Iterable[str],
805809
*,
806810
root_symbol: str | None = None,
807811
expiration_date: date | None = None,
@@ -811,15 +815,11 @@ async def get(
811815
page_offset: int | None = 0,
812816
) -> list[Self]: ...
813817

814-
@overload
815-
@classmethod
816-
async def get(cls, session: Session, symbols: str) -> Self: ...
817-
818818
@classmethod
819819
async def get(
820820
cls,
821821
session: Session,
822-
symbols: str | list[str],
822+
symbols: str | Iterable[str],
823823
*,
824824
root_symbol: str | None = None,
825825
expiration_date: date | None = None,
@@ -914,17 +914,17 @@ class Warrant(TastytradeData):
914914

915915
@overload
916916
@classmethod
917-
async def get(
918-
cls, session: Session, symbols: list[str] | None = None
919-
) -> list[Self]: ...
917+
async def get(cls, session: Session, symbols: str) -> Self: ... # type: ignore[overload-overlap]
920918

921919
@overload
922920
@classmethod
923-
async def get(cls, session: Session, symbols: str) -> Self: ...
921+
async def get(
922+
cls, session: Session, symbols: Iterable[str] | None = None
923+
) -> list[Self]: ...
924924

925925
@classmethod
926926
async def get(
927-
cls, session: Session, symbols: str | list[str] | None = None
927+
cls, session: Session, symbols: str | Iterable[str] | None = None
928928
) -> Self | list[Self]:
929929
"""
930930
Returns a list of Warrant objects from the given symbols, or a single

tastytrade/market_data.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import date, datetime
22
from decimal import Decimal
33
from enum import Enum
4+
from typing import Iterable
45

56
from tastytrade.order import InstrumentType
67
from tastytrade.session import Session
@@ -121,12 +122,12 @@ async def get_market_data(
121122

122123
async def get_market_data_by_type(
123124
session: Session,
124-
cryptocurrencies: list[str] | None = None,
125-
equities: list[str] | None = None,
126-
futures: list[str] | None = None,
127-
future_options: list[str] | None = None,
128-
indices: list[str] | None = None,
129-
options: list[str] | None = None,
125+
cryptocurrencies: Iterable[str] | None = None,
126+
equities: Iterable[str] | None = None,
127+
futures: Iterable[str] | None = None,
128+
future_options: Iterable[str] | None = None,
129+
indices: Iterable[str] | None = None,
130+
options: Iterable[str] | None = None,
130131
) -> list[MarketData]:
131132
"""
132133
Get market data for the given symbols grouped by instrument type.

tastytrade/market_sessions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import date, datetime
22
from enum import Enum
3+
from typing import Iterable
34

45
from pydantic import Field
56

@@ -67,7 +68,7 @@ class MarketCalendar(TastytradeData):
6768

6869

6970
async def get_market_sessions(
70-
session: Session, exchanges: list[ExchangeType]
71+
session: Session, exchanges: Iterable[ExchangeType]
7172
) -> list[MarketSession]:
7273
"""
7374
Retrieves a list of session timings for the given exchanges.

tastytrade/metrics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import date, datetime
22
from decimal import Decimal
3+
from typing import Iterable
34

45
from tastytrade.session import Session
56
from tastytrade.utils import TastytradeData, validate_and_parse
@@ -113,7 +114,7 @@ class MarketMetricInfo(TastytradeData):
113114

114115

115116
async def get_market_metrics(
116-
session: Session, symbols: list[str]
117+
session: Session, symbols: Iterable[str]
117118
) -> list[MarketMetricInfo]:
118119
"""
119120
Retrieves market metrics for the given symbols.

0 commit comments

Comments
 (0)