Monzo-API 1.2.6__tar.gz → 1.2.7__tar.gz
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.
- {monzo_api-1.2.6 → monzo_api-1.2.7/Monzo_API.egg-info}/PKG-INFO +1 -1
- {monzo_api-1.2.6/Monzo_API.egg-info → monzo_api-1.2.7}/PKG-INFO +1 -1
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/authentication.py +16 -14
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/account.py +2 -2
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/balance.py +1 -1
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/feed_item.py +1 -1
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/pot.py +9 -9
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/receipt.py +11 -11
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/helpers.py +2 -2
- {monzo_api-1.2.6 → monzo_api-1.2.7}/pyproject.toml +1 -9
- {monzo_api-1.2.6 → monzo_api-1.2.7}/tests/test_endpoints.py +9 -5
- {monzo_api-1.2.6 → monzo_api-1.2.7}/tests/test_httpio_errors.py +3 -4
- {monzo_api-1.2.6 → monzo_api-1.2.7}/LICENSE +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/Monzo_API.egg-info/SOURCES.txt +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/Monzo_API.egg-info/dependency_links.txt +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/Monzo_API.egg-info/top_level.txt +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/README.rst +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/__init__.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/__init__.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/attachment.py +3 -3
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/monzo.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/transaction.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/webhooks.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/endpoints/whoami.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/exceptions.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/handlers/__init__.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/handlers/filesystem.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/handlers/storage.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/monzo/httpio.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/setup.cfg +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/tests/test_authentication.py +0 -0
- {monzo_api-1.2.6 → monzo_api-1.2.7}/tests/test_payload.py +0 -0
|
@@ -14,6 +14,8 @@ from monzo.httpio import DEFAULT_TIMEOUT, REQUEST_RESPONSE_TYPE, HttpIO
|
|
|
14
14
|
MONZO_AUTH_URL = "https://auth.monzo.com"
|
|
15
15
|
MONZO_API_URL = "https://api.monzo.com"
|
|
16
16
|
|
|
17
|
+
logger: logging.Logger = logging.getLogger(name=__name__)
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
class Authentication:
|
|
19
21
|
"""
|
|
@@ -72,12 +74,12 @@ class Authentication:
|
|
|
72
74
|
Raises:
|
|
73
75
|
MonzoAuthenticationError On missing authorization token or mismatching state tokens
|
|
74
76
|
"""
|
|
75
|
-
|
|
77
|
+
logger.debug(msg="Attempting authentication")
|
|
76
78
|
if not authorization_token:
|
|
77
|
-
|
|
79
|
+
logger.debug(msg="Authentication - Missing token")
|
|
78
80
|
raise MonzoAuthenticationError("Code missing from response")
|
|
79
81
|
if state_token != self.state_token:
|
|
80
|
-
|
|
82
|
+
logger.debug(msg="Authentication - state token mismatch")
|
|
81
83
|
raise MonzoAuthenticationError("State tokens do not match")
|
|
82
84
|
tmp_file_name = "monzo"
|
|
83
85
|
tmp_file_path = PurePath(gettempdir(), tmp_file_name)
|
|
@@ -86,7 +88,7 @@ class Authentication:
|
|
|
86
88
|
|
|
87
89
|
def logout(self) -> None:
|
|
88
90
|
"""Invalidate the access token."""
|
|
89
|
-
|
|
91
|
+
logger.debug(msg="Invalidating token")
|
|
90
92
|
self.make_request(path="/oauth2/logout", method="post")
|
|
91
93
|
|
|
92
94
|
def make_request(
|
|
@@ -140,9 +142,9 @@ class Authentication:
|
|
|
140
142
|
Raises:
|
|
141
143
|
MonzoAuthenticationError: On lack of refresh token or failure to refresh a token
|
|
142
144
|
"""
|
|
143
|
-
|
|
145
|
+
logger.debug(msg="Fetching new token")
|
|
144
146
|
if not self.refresh_token:
|
|
145
|
-
|
|
147
|
+
logger.debug(msg="Unable to fetch new token without a refresh token")
|
|
146
148
|
raise MonzoAuthenticationError("Unable to refresh without a refresh token")
|
|
147
149
|
data = {
|
|
148
150
|
"grant_type": "refresh_token",
|
|
@@ -153,9 +155,9 @@ class Authentication:
|
|
|
153
155
|
conn = HttpIO(MONZO_API_URL)
|
|
154
156
|
try:
|
|
155
157
|
res = conn.post(path="/oauth2/token", data=data)
|
|
156
|
-
self._populate_tokens(res)
|
|
158
|
+
self._populate_tokens(response=res)
|
|
157
159
|
except MonzoError as exc:
|
|
158
|
-
|
|
160
|
+
logger.debug(msg="Failed to fetch new token")
|
|
159
161
|
raise MonzoAuthenticationError("Could not refresh the access token") from exc
|
|
160
162
|
|
|
161
163
|
@property
|
|
@@ -240,7 +242,7 @@ class Authentication:
|
|
|
240
242
|
Raises:
|
|
241
243
|
MonzoAuthenticationError On failure to create a token
|
|
242
244
|
"""
|
|
243
|
-
|
|
245
|
+
logger.debug(msg="Authentication - swapping authorization token for an access token")
|
|
244
246
|
data = {
|
|
245
247
|
"grant_type": "authorization_code",
|
|
246
248
|
"client_id": self._client_id,
|
|
@@ -249,10 +251,10 @@ class Authentication:
|
|
|
249
251
|
"code": authorization_token,
|
|
250
252
|
}
|
|
251
253
|
try:
|
|
252
|
-
res = self.make_request("/oauth2/token", authenticated=False, method="post", data=data)
|
|
253
|
-
self._populate_tokens(res)
|
|
254
|
+
res = self.make_request(path="/oauth2/token", authenticated=False, method="post", data=data)
|
|
255
|
+
self._populate_tokens(response=res)
|
|
254
256
|
except MonzoError as exc:
|
|
255
|
-
|
|
257
|
+
logger.debug(msg=f"Could not fetch access token {exc}")
|
|
256
258
|
raise MonzoAuthenticationError("Could not fetch a valid access token") from exc
|
|
257
259
|
|
|
258
260
|
def _populate_tokens(self, response: REQUEST_RESPONSE_TYPE) -> None:
|
|
@@ -262,7 +264,7 @@ class Authentication:
|
|
|
262
264
|
Args:
|
|
263
265
|
response: Response from an auth request.
|
|
264
266
|
"""
|
|
265
|
-
|
|
267
|
+
logger.debug(msg="Populating tokens")
|
|
266
268
|
self._access_token = response["data"]["access_token"]
|
|
267
269
|
self.access_token_expiry = response["data"]["expires_in"]
|
|
268
270
|
self._refresh_token = ""
|
|
@@ -285,5 +287,5 @@ class Authentication:
|
|
|
285
287
|
Args:
|
|
286
288
|
handler: Credential handler implementing Storage
|
|
287
289
|
"""
|
|
288
|
-
|
|
290
|
+
logger.debug(msg="Registered a new callback handler")
|
|
289
291
|
self._handlers.append(handler)
|
|
@@ -35,10 +35,10 @@ class Account(Monzo):
|
|
|
35
35
|
"_account_id",
|
|
36
36
|
"_auth",
|
|
37
37
|
"_balance",
|
|
38
|
+
"_closed",
|
|
38
39
|
"_created",
|
|
39
40
|
"_description",
|
|
40
41
|
"_has_balance",
|
|
41
|
-
"_closed",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
def __init__(
|
|
@@ -88,7 +88,7 @@ class Account(Monzo):
|
|
|
88
88
|
return next(
|
|
89
89
|
(
|
|
90
90
|
MONZO_ACCOUNT_TYPES[account_type]
|
|
91
|
-
for account_type in MONZO_ACCOUNT_TYPES
|
|
91
|
+
for account_type in MONZO_ACCOUNT_TYPES
|
|
92
92
|
if self.description.lower().startswith(account_type)
|
|
93
93
|
),
|
|
94
94
|
"UNKNOWN",
|
|
@@ -14,7 +14,7 @@ class Balance(Monzo):
|
|
|
14
14
|
along with the balance property. Otherwise, you can fetch an account balance using the fetch class method.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
__slots__ =
|
|
17
|
+
__slots__ = ("_balance", "_currency", "_spend_today", "_total_balance")
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
@@ -98,7 +98,7 @@ class FeedItem(Monzo):
|
|
|
98
98
|
}
|
|
99
99
|
if self._url:
|
|
100
100
|
data["url"] = self._url
|
|
101
|
-
for parameter in parameters
|
|
101
|
+
for parameter in parameters:
|
|
102
102
|
data[f"params[{parameter}]"] = parameters[parameter]
|
|
103
103
|
self._monzo_auth.make_request(path="/feed", method="POST", data=data)
|
|
104
104
|
|
|
@@ -19,22 +19,22 @@ class Pot(Monzo):
|
|
|
19
19
|
Class provides methods to fetch pots as well as depositing and withdrawing from pots.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
__slots__ =
|
|
23
|
-
"_pot_id",
|
|
24
|
-
"_name",
|
|
25
|
-
"_style",
|
|
22
|
+
__slots__ = (
|
|
26
23
|
"_balance",
|
|
27
|
-
"_currency",
|
|
28
24
|
"_created",
|
|
29
|
-
"
|
|
25
|
+
"_currency",
|
|
30
26
|
"_deleted",
|
|
31
27
|
"_goal_amount",
|
|
32
28
|
"_has_round_up",
|
|
33
|
-
"_round_up_multiplier",
|
|
34
|
-
"_pot_type",
|
|
35
29
|
"_locked",
|
|
36
30
|
"_locked_until",
|
|
37
|
-
|
|
31
|
+
"_name",
|
|
32
|
+
"_pot_id",
|
|
33
|
+
"_pot_type",
|
|
34
|
+
"_round_up_multiplier",
|
|
35
|
+
"_style",
|
|
36
|
+
"_updated",
|
|
37
|
+
)
|
|
38
38
|
|
|
39
39
|
def __init__(
|
|
40
40
|
self,
|
|
@@ -103,14 +103,14 @@ class Receipt(Monzo):
|
|
|
103
103
|
"""
|
|
104
104
|
|
|
105
105
|
__slots__ = (
|
|
106
|
-
"_external_id",
|
|
107
|
-
"_transaction_id",
|
|
108
|
-
"_total",
|
|
109
106
|
"_currency",
|
|
107
|
+
"_external_id",
|
|
110
108
|
"_items",
|
|
111
|
-
"_taxes",
|
|
112
|
-
"_payments",
|
|
113
109
|
"_merchant",
|
|
110
|
+
"_payments",
|
|
111
|
+
"_taxes",
|
|
112
|
+
"_total",
|
|
113
|
+
"_transaction_id",
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
def __init__(
|
|
@@ -363,9 +363,9 @@ class Receipt(Monzo):
|
|
|
363
363
|
receipt_data = res["data"]["receipt"]
|
|
364
364
|
receipt_items: list[ReceiptItem] = []
|
|
365
365
|
for item in receipt_data["items"]:
|
|
366
|
-
quantity = float(item
|
|
367
|
-
tax = item
|
|
368
|
-
unit = item
|
|
366
|
+
quantity = float(item.get("quantity", 0.0)) if "quantity" in item else 0.0
|
|
367
|
+
tax = item.get("tax", 0) if "tax" in item else 0
|
|
368
|
+
unit = item.get("unit", "") if "unit" in item else ""
|
|
369
369
|
receipt_item: ReceiptItem = ReceiptItem(
|
|
370
370
|
description=item["description"],
|
|
371
371
|
amount=item["amount"],
|
|
@@ -376,9 +376,9 @@ class Receipt(Monzo):
|
|
|
376
376
|
)
|
|
377
377
|
|
|
378
378
|
for sub_item in item["sub_items"]:
|
|
379
|
-
sub_item_quantity = sub_item
|
|
380
|
-
sub_item_tax = sub_item
|
|
381
|
-
sub_item_unit = sub_item
|
|
379
|
+
sub_item_quantity = sub_item.get("quantity", 0.0) if "quantity" in sub_item else 0.0
|
|
380
|
+
sub_item_tax = sub_item.get("tax", 0) if "tax" in sub_item else 0
|
|
381
|
+
sub_item_unit = sub_item.get("unit", "") if "unit" in sub_item else ""
|
|
382
382
|
receipt_sub_item = ReceiptItem(
|
|
383
383
|
description=sub_item["description"],
|
|
384
384
|
amount=sub_item["amount"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Helper functions."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def create_date(date_str: str) -> datetime:
|
|
@@ -13,7 +13,7 @@ def create_date(date_str: str) -> datetime:
|
|
|
13
13
|
Returns:
|
|
14
14
|
Converted date and time
|
|
15
15
|
"""
|
|
16
|
-
return datetime.strptime(date_str[:19], "%Y-%m-%dT%H:%M:%S")
|
|
16
|
+
return datetime.strptime(date_str[:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=UTC)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def format_date(date: datetime) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Monzo-API"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.7"
|
|
4
4
|
authors = [{ name = "Peter McDonald", email = "git@petermcdonald.co.uk"}]
|
|
5
5
|
description = "Package to interact with the API provided by Monzo bank"
|
|
6
6
|
keywords = ["monzo", "bank", "api"]
|
|
@@ -29,19 +29,11 @@ documentation = "https://monzo-api.readthedocs.io"
|
|
|
29
29
|
|
|
30
30
|
[dependency-groups]
|
|
31
31
|
dev = [
|
|
32
|
-
"cosmic-ray>=8.4.6",
|
|
33
|
-
"idna>=3.16", # To resolve security vulnerabilities in dependencies pip-audit
|
|
34
|
-
"pip-audit>=2.9.0",
|
|
35
|
-
"pygments>=2.20.0", # To resolve security vulnerabilities in dependencies pytest sphinx
|
|
36
32
|
"pytest>=9.0.3",
|
|
37
33
|
"pytest-cov>=7.1.0",
|
|
38
34
|
"pytest-mock>=3.15.1",
|
|
39
|
-
"ruff>=0.15.10",
|
|
40
35
|
"sphinx>=9.1.0",
|
|
41
36
|
"sphinx-rtd-theme>=3.1.0",
|
|
42
|
-
"troml>=0.4.1",
|
|
43
|
-
"ty>=0.0.29",
|
|
44
|
-
"urllib3>=2.7.0", # To resolve security vulnerabilities in dependencies pip-audit
|
|
45
37
|
]
|
|
46
38
|
|
|
47
39
|
[tool.pytest.ini_options]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for endpoints."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
@@ -225,7 +225,7 @@ class TestEndPoints:
|
|
|
225
225
|
{"bills": -2775},
|
|
226
226
|
"bills",
|
|
227
227
|
{},
|
|
228
|
-
datetime(2022, 8, 9, 14, 15, 1),
|
|
228
|
+
datetime(year=2022, month=8, day=9, hour=14, minute=15, second=1, tzinfo=UTC),
|
|
229
229
|
"GBP",
|
|
230
230
|
"123ABC",
|
|
231
231
|
"",
|
|
@@ -250,9 +250,9 @@ class TestEndPoints:
|
|
|
250
250
|
"",
|
|
251
251
|
False,
|
|
252
252
|
"mastercard",
|
|
253
|
-
datetime(2022, 8, 10, 0, 30, 40),
|
|
253
|
+
datetime(year=2022, month=8, day=10, hour=0, minute=30, second=40, tzinfo=UTC),
|
|
254
254
|
"tx_123ABC1",
|
|
255
|
-
datetime(2022, 8, 10, 0, 30, 40),
|
|
255
|
+
datetime(year=2022, month=8, day=10, hour=0, minute=30, second=40, tzinfo=UTC),
|
|
256
256
|
"user_ABC123",
|
|
257
257
|
),
|
|
258
258
|
],
|
|
@@ -358,7 +358,11 @@ class TestEndPoints:
|
|
|
358
358
|
|
|
359
359
|
auth.register_callback_handler(handler)
|
|
360
360
|
|
|
361
|
-
transactions = Transaction.fetch(
|
|
361
|
+
transactions = Transaction.fetch(
|
|
362
|
+
auth=auth,
|
|
363
|
+
account_id=expected_account_id,
|
|
364
|
+
since=datetime.now(tz=UTC),
|
|
365
|
+
)
|
|
362
366
|
assert len(transactions) == 1
|
|
363
367
|
transaction = transactions[0]
|
|
364
368
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from email.message import Message
|
|
1
2
|
from unittest.mock import patch
|
|
2
3
|
from urllib.error import HTTPError
|
|
3
4
|
|
|
@@ -5,12 +6,10 @@ import pytest
|
|
|
5
6
|
|
|
6
7
|
from monzo.exceptions import MonzoGeneralError
|
|
7
8
|
from monzo.httpio import HttpIO
|
|
8
|
-
from email.message import Message
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def test_unknown_status_code_raises_monzogeneralerror():
|
|
12
12
|
http = HttpIO("https://example.com")
|
|
13
13
|
error = HTTPError(url="https://example.com/test", code=418, msg="teapot", hdrs=Message(), fp=None)
|
|
14
|
-
with patch("monzo.httpio.urlopen", side_effect=error):
|
|
15
|
-
|
|
16
|
-
http.get("/test")
|
|
14
|
+
with patch("monzo.httpio.urlopen", side_effect=error), pytest.raises(MonzoGeneralError):
|
|
15
|
+
http.get("/test")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|