Skip to content

Commit df9a9f9

Browse files
authored
add serialization for sessions (#197)
1 parent a2d9ce2 commit df9a9f9

File tree

6 files changed

+46
-7
lines changed

6 files changed

+46
-7
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
project = "tastytrade"
1414
copyright = "2024, Graeme Holliday"
1515
author = "Graeme Holliday"
16-
release = "9.6"
16+
release = "9.7"
1717

1818
# -- General configuration ---------------------------------------------------
1919
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "tastytrade"
7-
version = "9.6"
7+
version = "9.7"
88
description = "An unofficial, sync/async SDK for Tastytrade!"
99
readme = "README.md"
1010
requires-python = ">=3.9"

tastytrade/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
BACKTEST_URL = "https://backtester.vast.tastyworks.com"
55
CERT_URL = "https://api.cert.tastyworks.com"
66
VAST_URL = "https://vast.tastyworks.com"
7-
VERSION = "9.6"
7+
VERSION = "9.7"
88

99
logger = logging.getLogger(__name__)
1010
logger.setLevel(logging.DEBUG)

tastytrade/session.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from datetime import date, datetime
22
from typing import Any, Optional, Union
3+
from typing_extensions import Self
34

45
import httpx
6+
import json
7+
from httpx import AsyncClient, Client
58

69
from tastytrade import API_URL, CERT_URL
710
from tastytrade.utils import TastytradeError, TastytradeJsonDataclass, validate_response
@@ -302,7 +305,7 @@ def __init__(
302305
"Content-Type": "application/json",
303306
}
304307
#: httpx client for sync requests
305-
self.sync_client = httpx.Client(
308+
self.sync_client = Client(
306309
base_url=(CERT_URL if is_test else API_URL), headers=headers
307310
)
308311
if two_factor_authentication is not None:
@@ -323,9 +326,8 @@ def __init__(
323326
#: A single-use token which can be used to login without a password
324327
self.remember_token = json["data"].get("remember-token")
325328
self.sync_client.headers.update({"Authorization": self.session_token})
326-
self.validate()
327329
#: httpx client for async requests
328-
self.async_client = httpx.AsyncClient(
330+
self.async_client = AsyncClient(
329331
base_url=self.sync_client.base_url, headers=self.sync_client.headers.copy()
330332
)
331333

@@ -440,3 +442,34 @@ def get_2fa_info(self) -> TwoFactorInfo:
440442
"""
441443
data = self._get("/users/me/two-factor-method")
442444
return TwoFactorInfo(**data)
445+
446+
def serialize(self) -> str:
447+
"""
448+
Serializes the session to a string, useful for storing
449+
a session for later use.
450+
Could be used with pickle, Redis, etc.
451+
"""
452+
attrs = self.__dict__.copy()
453+
del attrs["async_client"]
454+
del attrs["sync_client"]
455+
attrs["user"] = attrs["user"].model_dump()
456+
return json.dumps(attrs)
457+
458+
@classmethod
459+
def deserialize(cls, serialized: str) -> Self:
460+
"""
461+
Create a new Session object from a serialized string.
462+
"""
463+
deserialized = json.loads(serialized)
464+
deserialized["user"] = User(**deserialized["user"])
465+
self = cls.__new__(cls)
466+
self.__dict__ = deserialized
467+
base_url = CERT_URL if self.is_test else API_URL
468+
headers = {
469+
"Accept": "application/json",
470+
"Content-Type": "application/json",
471+
"Authorization": self.session_token,
472+
}
473+
self.sync_client = Client(base_url=base_url, headers=headers)
474+
self.async_client = AsyncClient(base_url=base_url, headers=headers)
475+
return self

tests/test_session.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ def test_destroy(credentials):
2525
async def test_destroy_async(credentials):
2626
session = Session(*credentials)
2727
await session.a_destroy()
28+
29+
30+
def test_serialize_deserialize(session):
31+
data = session.serialize()
32+
obj = Session.deserialize(data)
33+
assert set(obj.__dict__.keys()) == set(session.__dict__.keys())

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)