Skip to content

Commit 3fe6ba3

Browse files
authored
Adds client token age fetching, plus all related documentation (#106)
1 parent a5fd35a commit 3fe6ba3

File tree

6 files changed

+67
-0
lines changed

6 files changed

+67
-0
lines changed

docs/auth.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,29 @@ pip's executable locations to your ``$PATH``. If you're having a hard time, feel
120120
free to ask for help on our `Discord server <https://discord.gg/BEr6y6Xqyv>`__.
121121

122122

123+
-------------------------
124+
Notes on Token Expiration
125+
-------------------------
126+
127+
Tokens are only good for seven days. Under the hood, each token is actually good
128+
for *thirty minutes*, but the library transparently issues a request to Schwab
129+
to fetch a new token when it's time to do so. (This means the "token file" would
130+
more accurately be called the "file containing the specifications required to
131+
generate new tokens," but "token file" is simpler.) Once seven days have passed
132+
since the token was originally created, they will refuse to grant new thirty
133+
minute tokens, and you need to delete your old token file and create a new one
134+
by following your preferred token creation flow.
135+
136+
In practice, this means most users will want to adopt some sort of proactive
137+
token refreshing method. For instance, if you trade during the weekdays, you may
138+
want to delete and recreate your token on Sunday before the markets open.
139+
140+
For users wanting to craft more custom workflows, the client :meth:`exposes the
141+
age of the token <schwab.client.Client.token_age>`. Note, however, that the
142+
seven day token age restriction is implemented by Schwab, and so the token may
143+
become expired sooner *or* later than seven days.
144+
145+
123146
----------------------
124147
Advanced Functionality
125148
----------------------
@@ -138,6 +161,7 @@ instead.
138161

139162
.. autofunction:: schwab.auth.client_from_access_functions
140163

164+
141165
---------------
142166
Troubleshooting
143167
---------------

docs/client.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ specification can be set using this method:
185185
.. automethod:: schwab.client.Client.set_timeout
186186

187187

188+
+++++++++
189+
Token Age
190+
+++++++++
191+
192+
.. automethod:: schwab.client.Client.token_age
193+
194+
188195
++++++++++++
189196
Account Info
190197
++++++++++++

schwab/auth.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ def from_loaded_token(cls, token, unwrapped_token_write_func):
8585
token['creation_timestamp'],
8686
unwrapped_token_write_func)
8787

88+
def token_age(self):
89+
'''Returns the number of second elapsed since this token was initially
90+
created.'''
91+
return int(time.time()) - self.creation_timestamp
92+
8893
def wrapped_token_write_func(self):
8994
'''
9095
Returns a version of the unwrapped write function which wraps the token

schwab/client/base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ def set_timeout(self, timeout):
116116
examples.'''
117117
self.session.timeout = timeout
118118

119+
def token_age(self):
120+
'''Get the age of the token used to create this client, in seconds. For
121+
users who prefer to proactively delete their token files before the
122+
become expired, this method can offer a hint for when to do so.
123+
124+
Note that the actual expiration is governed by Schwab's internal
125+
implementation, and the token might become expired sooner *or* later
126+
than the documented seven day term.'''
127+
return self.token_metadata.token_age()
128+
119129

120130
##########################################################################
121131
# Accounts

tests/auth_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,14 @@ def test_reject_tokens_without_creation_timestamp(self):
447447
with self.assertRaisesRegex(ValueError, 'token format has changed'):
448448
metadata = auth.TokenMetadata.from_loaded_token(
449449
{'token': 'yes'}, lambda t: None)
450+
451+
452+
@no_duplicates
453+
@patch('time.time', unittest.mock.MagicMock(return_value=MOCK_NOW))
454+
def test_token_age(self):
455+
token = {'token': 'yes', 'creation_timestamp': TOKEN_CREATION_TIMESTAMP}
456+
457+
metadata = auth.TokenMetadata.from_loaded_token(
458+
token, unwrapped_token_write_func=None)
459+
self.assertEqual(metadata.token_age(),
460+
MOCK_NOW - TOKEN_CREATION_TIMESTAMP)

tests/client_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ def test_set_timeout(self):
9696
self.assertEqual(timeout, self.client.session.timeout)
9797

9898

99+
def test_token_age(self):
100+
token_metadata = MagicMock()
101+
token_metadata.token_age.return_value = 1000
102+
client = self.client_class(
103+
API_KEY, self.mock_session, token_metadata=token_metadata)
104+
105+
self.assertEqual(client.token_age(), 1000)
106+
107+
108+
99109
# get_account
100110

101111

0 commit comments

Comments
 (0)