diff --git a/docs/conf.py b/docs/conf.py index 4b71af0..b712e51 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ project = "tastytrade" copyright = "2024, Graeme Holliday" author = "Graeme Holliday" -release = "9.6" +release = "9.7" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyproject.toml b/pyproject.toml index c618bdb..e3c8173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tastytrade" -version = "9.6" +version = "9.7" description = "An unofficial, sync/async SDK for Tastytrade!" readme = "README.md" requires-python = ">=3.9" diff --git a/tastytrade/__init__.py b/tastytrade/__init__.py index 8485a66..223122e 100644 --- a/tastytrade/__init__.py +++ b/tastytrade/__init__.py @@ -4,7 +4,7 @@ BACKTEST_URL = "https://backtester.vast.tastyworks.com" CERT_URL = "https://api.cert.tastyworks.com" VAST_URL = "https://vast.tastyworks.com" -VERSION = "9.6" +VERSION = "9.7" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) diff --git a/tastytrade/session.py b/tastytrade/session.py index 58a7df8..5306913 100644 --- a/tastytrade/session.py +++ b/tastytrade/session.py @@ -1,7 +1,10 @@ from datetime import date, datetime from typing import Any, Optional, Union +from typing_extensions import Self import httpx +import json +from httpx import AsyncClient, Client from tastytrade import API_URL, CERT_URL from tastytrade.utils import TastytradeError, TastytradeJsonDataclass, validate_response @@ -302,7 +305,7 @@ def __init__( "Content-Type": "application/json", } #: httpx client for sync requests - self.sync_client = httpx.Client( + self.sync_client = Client( base_url=(CERT_URL if is_test else API_URL), headers=headers ) if two_factor_authentication is not None: @@ -323,9 +326,8 @@ def __init__( #: A single-use token which can be used to login without a password self.remember_token = json["data"].get("remember-token") self.sync_client.headers.update({"Authorization": self.session_token}) - self.validate() #: httpx client for async requests - self.async_client = httpx.AsyncClient( + self.async_client = AsyncClient( base_url=self.sync_client.base_url, headers=self.sync_client.headers.copy() ) @@ -440,3 +442,34 @@ def get_2fa_info(self) -> TwoFactorInfo: """ data = self._get("/users/me/two-factor-method") return TwoFactorInfo(**data) + + def serialize(self) -> str: + """ + Serializes the session to a string, useful for storing + a session for later use. + Could be used with pickle, Redis, etc. + """ + attrs = self.__dict__.copy() + del attrs["async_client"] + del attrs["sync_client"] + attrs["user"] = attrs["user"].model_dump() + return json.dumps(attrs) + + @classmethod + def deserialize(cls, serialized: str) -> Self: + """ + Create a new Session object from a serialized string. + """ + deserialized = json.loads(serialized) + deserialized["user"] = User(**deserialized["user"]) + self = cls.__new__(cls) + self.__dict__ = deserialized + base_url = CERT_URL if self.is_test else API_URL + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": self.session_token, + } + self.sync_client = Client(base_url=base_url, headers=headers) + self.async_client = AsyncClient(base_url=base_url, headers=headers) + return self diff --git a/tests/test_session.py b/tests/test_session.py index e29e216..071b550 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -25,3 +25,9 @@ def test_destroy(credentials): async def test_destroy_async(credentials): session = Session(*credentials) await session.a_destroy() + + +def test_serialize_deserialize(session): + data = session.serialize() + obj = Session.deserialize(data) + assert set(obj.__dict__.keys()) == set(session.__dict__.keys()) diff --git a/uv.lock b/uv.lock index a545b88..4608846 100644 --- a/uv.lock +++ b/uv.lock @@ -546,7 +546,7 @@ wheels = [ [[package]] name = "tastytrade" -version = "9.5" +version = "9.7" source = { editable = "." } dependencies = [ { name = "httpx" },