Skip to content

Commit bd1159f

Browse files
committed
Create TokenStore abstraction
1 parent 59da6f1 commit bd1159f

2 files changed

Lines changed: 55 additions & 44 deletions

File tree

santander_sdk/api_client/auth.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import abc
12
from datetime import datetime, timedelta
23

34
from requests import HTTPError, JSONDecodeError
@@ -8,19 +9,55 @@
89
from santander_sdk.api_client.exceptions import SantanderRequestError
910

1011

12+
class TokenStore(abc.ABC):
13+
@abc.abstractmethod
14+
def get(self) -> str | None: ...
15+
16+
@abc.abstractmethod
17+
def set(self, token: str, expires_in: timedelta) -> None: ...
18+
19+
20+
class InMemoryTokenStore(TokenStore):
21+
def __init__(self, offset=timedelta(seconds=60)):
22+
self._token = None
23+
self._expires_at = None
24+
self._offset = offset
25+
26+
def get(self):
27+
if self._is_expired():
28+
return None
29+
30+
return self._token
31+
32+
def _is_expired(self):
33+
if self._expires_at is None:
34+
return True
35+
36+
return self._expires_at - self._offset < datetime.now()
37+
38+
def set(self, token: str, expires_in: timedelta):
39+
self._token = token
40+
self._expires_at = datetime.now() + expires_in
41+
42+
1143
class SantanderAuth(AuthBase):
1244
TOKEN_ENDPOINT = "/auth/oauth/v2/token"
1345
TIMEOUT_SECS = 60
1446
BEFORE_EXPIRE_TOKEN = timedelta(seconds=60)
1547

16-
def __init__(self, base_url, client_id, client_secret, cert_path):
48+
def __init__(
49+
self,
50+
base_url,
51+
client_id,
52+
client_secret,
53+
cert_path,
54+
token_store=InMemoryTokenStore(),
55+
):
1756
self.base_url = base_url
1857
self.client_id = client_id
1958
self.client_secret = client_secret
2059
self.cert_path = cert_path
21-
22-
self._token = None
23-
self.expires_at = None
60+
self.token_store = token_store
2461

2562
@classmethod
2663
def from_config(cls, config: SantanderClientConfiguration):
@@ -38,14 +75,12 @@ def __call__(self, r):
3875

3976
@property
4077
def token(self):
41-
if self.is_expired:
42-
self.renew()
43-
44-
return self._token
78+
token = self.token_store.get()
79+
if not token:
80+
token, expires_in = self.renew()
81+
self.token_store.set(token, expires_in)
4582

46-
@token.setter
47-
def token(self, values):
48-
self._token, self.expires_at = values
83+
return token
4984

5085
def renew(self):
5186
session = BaseURLSession(base_url=self.base_url)
@@ -76,14 +111,4 @@ def renew(self):
76111
) from e
77112

78113
data = response.json()
79-
self.token = (
80-
data["access_token"],
81-
datetime.now() + timedelta(seconds=data["expires_in"]),
82-
)
83-
84-
@property
85-
def is_expired(self):
86-
if not self.expires_at:
87-
return True
88-
89-
return datetime.now() > self.expires_at - self.BEFORE_EXPIRE_TOKEN
114+
return data["access_token"], timedelta(seconds=data["expires_in"])

tests/test_auth_unit.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from re import compile as regex
2-
from datetime import datetime
2+
from datetime import timedelta
33

44
import pytest
55
from freezegun import freeze_time
@@ -51,34 +51,20 @@ def test_renew_when_token_empty(auth, responses):
5151
assert req.headers["X-Application-Key"] == auth.client_id
5252

5353

54-
@freeze_time("2025-02-13 10:05")
5554
def test_renew_token_when_expired(auth, responses):
5655
responses.add(
5756
responses.POST,
5857
regex(".+/auth/oauth/v2/token"),
59-
json={"access_token": "NEW_VALID_TOKEN", "expires_in": 120},
58+
json={"access_token": "FRESH_TOKEN", "expires_in": 120},
6059
)
61-
auth.token = "VALID_TOKEN", datetime(2025, 2, 13, 10)
60+
with freeze_time("2025-02-13 10:00"):
61+
auth.token_store.set("EXPIRED_TOKEN", timedelta(0))
6262

63-
req = PreparedRequest()
64-
req.prepare("GET", "https://api.santander.com.br/orders", auth=auth)
63+
with freeze_time("2025-02-13 10:01"):
64+
req = PreparedRequest()
65+
req.prepare("GET", "https://api.santander.com.br/orders", auth=auth)
6566

66-
assert req.headers["Authorization"] == "Bearer NEW_VALID_TOKEN"
67-
assert auth.expires_at == datetime(2025, 2, 13, 10, 7)
68-
69-
70-
@freeze_time("2025-02-13 10:00")
71-
@pytest.mark.parametrize(
72-
"expires_at,expected",
73-
[
74-
(None, True),
75-
(datetime(2025, 2, 13, 10, 1), False),
76-
(datetime(2025, 2, 13, 10, 0, 59), True),
77-
],
78-
)
79-
def test_is_expired(auth, expires_at, expected):
80-
auth.expires_at = expires_at
81-
assert auth.is_expired is expected
67+
assert req.headers["Authorization"] == "Bearer FRESH_TOKEN"
8268

8369

8470
def test_invalid_credentials(auth, responses):

0 commit comments

Comments
 (0)