beancount-gocardless 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beancount_gocardless/client.py +74 -15
- {beancount_gocardless-0.1.7.dist-info → beancount_gocardless-0.1.8.dist-info}/METADATA +1 -1
- beancount_gocardless-0.1.8.dist-info/RECORD +9 -0
- beancount_gocardless-0.1.7.dist-info/RECORD +0 -9
- {beancount_gocardless-0.1.7.dist-info → beancount_gocardless-0.1.8.dist-info}/LICENSE +0 -0
- {beancount_gocardless-0.1.7.dist-info → beancount_gocardless-0.1.8.dist-info}/WHEEL +0 -0
- {beancount_gocardless-0.1.7.dist-info → beancount_gocardless-0.1.8.dist-info}/entry_points.txt +0 -0
beancount_gocardless/client.py
CHANGED
|
@@ -7,22 +7,24 @@ import logging
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def strip_headers_hook(response, *args, **kwargs):
|
|
11
11
|
to_preserve = [
|
|
12
12
|
"Content-Type",
|
|
13
13
|
"Date",
|
|
14
14
|
"Content-Encoding",
|
|
15
15
|
"Content-Language",
|
|
16
|
-
"ETag",
|
|
17
16
|
"Last-Modified",
|
|
18
17
|
]
|
|
19
18
|
deleted = set()
|
|
20
19
|
to_preserve_lower = [h.lower() for h in to_preserve]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
header_keys_to_check = response.headers.copy().keys()
|
|
21
|
+
for header in header_keys_to_check:
|
|
22
|
+
if header.lower() in to_preserve_lower:
|
|
23
|
+
continue
|
|
24
|
+
else:
|
|
25
|
+
response.headers.pop(header, None)
|
|
24
26
|
deleted.add(header)
|
|
25
|
-
logger.
|
|
27
|
+
logger.debug("Deleted headers: %s", ", ".join(deleted))
|
|
26
28
|
return response
|
|
27
29
|
|
|
28
30
|
|
|
@@ -39,7 +41,7 @@ class CacheOptions(TypedDict, total=False):
|
|
|
39
41
|
|
|
40
42
|
cache_name: requests_cache.StrOrPath
|
|
41
43
|
backend: Optional[requests_cache.BackendSpecifier]
|
|
42
|
-
expire_after:
|
|
44
|
+
expire_after: int
|
|
43
45
|
old_data_on_error: bool
|
|
44
46
|
|
|
45
47
|
|
|
@@ -80,7 +82,6 @@ class BaseService:
|
|
|
80
82
|
"old_data_on_error": True,
|
|
81
83
|
"match_headers": False,
|
|
82
84
|
"cache_control": False,
|
|
83
|
-
"response_hook": cleanup_headers,
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
def __init__(
|
|
@@ -99,21 +100,78 @@ class BaseService:
|
|
|
99
100
|
"""
|
|
100
101
|
self.secret_id = secret_id
|
|
101
102
|
self.secret_key = secret_key
|
|
102
|
-
self.
|
|
103
|
+
self._token = None
|
|
103
104
|
merged_options = {**self.DEFAULT_CACHE_OPTIONS, **(cache_options or {})}
|
|
104
105
|
self.session = requests_cache.CachedSession(**merged_options)
|
|
106
|
+
self.session.hooks["response"].append(strip_headers_hook)
|
|
105
107
|
|
|
106
|
-
def
|
|
108
|
+
def check_cache_status(self, method: str, url: str, params=None, data=None) -> dict:
|
|
107
109
|
"""
|
|
108
|
-
|
|
110
|
+
Attempts to predict the cache status for a given request.
|
|
111
|
+
|
|
112
|
+
NOTE: This is an approximation and relies on internal mechanisms
|
|
113
|
+
that might change. It also performs I/O to check the cache.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
method (str): HTTP method ("GET", "POST", etc.).
|
|
117
|
+
endpoint (str): API endpoint.
|
|
118
|
+
params (dict, optional): URL parameters.
|
|
119
|
+
data (dict, optional): Request body data.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
dict: Information about the potential cache state:
|
|
123
|
+
{'key_exists': bool, 'is_expired': Optional[bool], 'cache_key': str}
|
|
124
|
+
'is_expired' is None if the key doesn't exist or expiration
|
|
125
|
+
cannot be reliably determined without full retrieval.
|
|
126
|
+
"""
|
|
127
|
+
headers = {"Authorization": f"Bearer {self.token}"}
|
|
128
|
+
|
|
129
|
+
req = requests.Request(method, url, params=params, data=data, headers=headers)
|
|
130
|
+
prepared_request: requests.PreparedRequest = self.session.prepare_request(req)
|
|
131
|
+
cache = self.session.cache
|
|
132
|
+
cache_key = cache.create_key(prepared_request)
|
|
133
|
+
key_exists = cache.contains(cache_key)
|
|
134
|
+
is_expired = None
|
|
135
|
+
|
|
136
|
+
if key_exists:
|
|
137
|
+
try:
|
|
138
|
+
# Try to get the response object without triggering expiration side effects
|
|
139
|
+
# Note: This still reads from the cache backend (I/O)
|
|
140
|
+
cached_response = cache.get_response(cache_key)
|
|
141
|
+
if cached_response:
|
|
142
|
+
# is_expired is a property calculated on the CachedResponse
|
|
143
|
+
is_expired = cached_response.is_expired
|
|
144
|
+
else:
|
|
145
|
+
# get_response might return None if item expired and configured to delete
|
|
146
|
+
# Or if backend consistency issue. Treat as expired/absent.
|
|
147
|
+
key_exists = False # Correct the state if get_response fails
|
|
148
|
+
is_expired = True # Assume expired if get_response returns None for existing key
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.error(
|
|
151
|
+
f"Error checking expiration for cache key {cache_key}: {e}"
|
|
152
|
+
)
|
|
153
|
+
# Cannot determine expiration reliably
|
|
154
|
+
is_expired = None # Mark as unknown
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"key_exists": key_exists,
|
|
158
|
+
"is_expired": is_expired,
|
|
159
|
+
"cache_key": cache_key,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def token(self):
|
|
164
|
+
"""
|
|
165
|
+
Ensure a token exists. Gets a new token if one doesn't exist.
|
|
109
166
|
Nordigen tokens don't currently have a refresh mechanism, so this just gets a new one if needed.
|
|
110
167
|
"""
|
|
111
|
-
if not self.
|
|
168
|
+
if not self._token:
|
|
112
169
|
self.get_token()
|
|
170
|
+
return self._token
|
|
113
171
|
|
|
114
172
|
def get_token(self):
|
|
115
173
|
"""
|
|
116
|
-
Fetch a new API access token using credentials. Sets the `self.
|
|
174
|
+
Fetch a new API access token using credentials. Sets the `self._token` attribute.
|
|
117
175
|
|
|
118
176
|
Raises:
|
|
119
177
|
HttpServiceException: If the API request fails.
|
|
@@ -123,7 +181,7 @@ class BaseService:
|
|
|
123
181
|
data={"secret_id": self.secret_id, "secret_key": self.secret_key},
|
|
124
182
|
)
|
|
125
183
|
self._handle_response(response)
|
|
126
|
-
self.
|
|
184
|
+
self._token = response.json()["access"]
|
|
127
185
|
|
|
128
186
|
def _handle_response(self, response):
|
|
129
187
|
"""
|
|
@@ -157,9 +215,10 @@ class BaseService:
|
|
|
157
215
|
HttpServiceException: If the API request fails.
|
|
158
216
|
"""
|
|
159
217
|
url = f"{self.BASE_URL}{endpoint}"
|
|
160
|
-
self._ensure_token_valid()
|
|
161
218
|
headers = {"Authorization": f"Bearer {self.token}"}
|
|
162
219
|
|
|
220
|
+
status = self.check_cache_status(method, url, params, data)
|
|
221
|
+
logger.debug(f"{endpoint}: {'expired' if status["is_expired"] else 'cache ok'}")
|
|
163
222
|
response = self.session.request(
|
|
164
223
|
method, url, headers=headers, params=params, data=data
|
|
165
224
|
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
beancount_gocardless/__init__.py,sha256=Rf2-pfuaXaXPwBu3yEn2uXyOQ6uLyGxljJ5hoTCss5Y,100
|
|
2
|
+
beancount_gocardless/cli.py,sha256=ZdsdknScEOlUq_7rI0ixzN1UDh1dgUokzTzO_3WySqY,2407
|
|
3
|
+
beancount_gocardless/client.py,sha256=g2Px7mcM34ibEAVzbxUtCTVrGWO1sm_CmCL-PXySHro,15764
|
|
4
|
+
beancount_gocardless/importer.py,sha256=tjNKPCYFddR62YDAXm6rfLqcVNeiGKWbVAEyoPWPekg,11151
|
|
5
|
+
beancount_gocardless-0.1.8.dist-info/LICENSE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
|
|
6
|
+
beancount_gocardless-0.1.8.dist-info/METADATA,sha256=-WXcAySDk-rankLsjnn9keWLVrrscByJdwl2n1h1bkw,2634
|
|
7
|
+
beancount_gocardless-0.1.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
8
|
+
beancount_gocardless-0.1.8.dist-info/entry_points.txt,sha256=fmhiRcNVrum0p30f5YNqvIYVEPXYsS5cP1xNkVmdn8k,70
|
|
9
|
+
beancount_gocardless-0.1.8.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
beancount_gocardless/__init__.py,sha256=Rf2-pfuaXaXPwBu3yEn2uXyOQ6uLyGxljJ5hoTCss5Y,100
|
|
2
|
-
beancount_gocardless/cli.py,sha256=ZdsdknScEOlUq_7rI0ixzN1UDh1dgUokzTzO_3WySqY,2407
|
|
3
|
-
beancount_gocardless/client.py,sha256=ubHY0oozTJD5Tb5hUH2u4aEgwymb3Y-bCmy-gtnMdVY,13019
|
|
4
|
-
beancount_gocardless/importer.py,sha256=tjNKPCYFddR62YDAXm6rfLqcVNeiGKWbVAEyoPWPekg,11151
|
|
5
|
-
beancount_gocardless-0.1.7.dist-info/LICENSE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
|
|
6
|
-
beancount_gocardless-0.1.7.dist-info/METADATA,sha256=FM32PtLlyhpogNVVGS3ypt5LpvjNUgPhcnIFZ6NadEw,2634
|
|
7
|
-
beancount_gocardless-0.1.7.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
8
|
-
beancount_gocardless-0.1.7.dist-info/entry_points.txt,sha256=fmhiRcNVrum0p30f5YNqvIYVEPXYsS5cP1xNkVmdn8k,70
|
|
9
|
-
beancount_gocardless-0.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{beancount_gocardless-0.1.7.dist-info → beancount_gocardless-0.1.8.dist-info}/entry_points.txt
RENAMED
|
File without changes
|