Skip to content

Commit b078ca3

Browse files
committed
Improve type hints
1 parent ce1fa14 commit b078ca3

File tree

8 files changed

+178
-117
lines changed

8 files changed

+178
-117
lines changed

minfraud/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def __init__(
382382
is_prepaid: Optional[bool] = None,
383383
is_virtual: Optional[bool] = None,
384384
# pylint:disable=redefined-builtin
385-
type: Optional[str] = None,
385+
type: Optional[str] = None, # noqa: A002
386386
) -> None:
387387
"""Initialize a CreditCard instance."""
388388
self.issuer = Issuer(**(issuer or {}))

minfraud/request.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@
264264
}
265265

266266

267-
def prepare_report(request: dict[str, Any], validate: bool):
267+
def prepare_report(request: dict[str, Any], validate: bool) -> dict[str, Any]:
268268
"""Validate and prepare minFraud report."""
269269
cleaned_request = _copy_and_clean(request)
270270
if validate:
@@ -279,7 +279,7 @@ def prepare_transaction(
279279
request: dict[str, Any],
280280
validate: bool,
281281
hash_email: bool,
282-
):
282+
) -> dict[str, Any]:
283283
"""Validate and prepare minFraud transaction."""
284284
cleaned_request = _copy_and_clean(request)
285285
if validate:
@@ -306,7 +306,7 @@ def _copy_and_clean(data: Any) -> Any:
306306
return data
307307

308308

309-
def clean_credit_card(credit_card) -> None:
309+
def clean_credit_card(credit_card: dict[str, Any]) -> None:
310310
"""Clean the credit_card input of a transaction request."""
311311
last4 = credit_card.pop("last_4_digits", None)
312312
if last4:
@@ -318,7 +318,7 @@ def clean_credit_card(credit_card) -> None:
318318
credit_card["last_digits"] = last4
319319

320320

321-
def maybe_hash_email(transaction) -> None:
321+
def maybe_hash_email(transaction: dict[str, Any]) -> None:
322322
"""Hash email address in transaction, if present."""
323323
try:
324324
email = transaction["email"]

minfraud/validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from decimal import Decimal
1515
from typing import Optional
1616

17-
from email_validator import validate_email # type: ignore
17+
from email_validator import validate_email
1818
from voluptuous import (
1919
All,
2020
Any,

minfraud/webservice.py

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Client for minFraud Score, Insights, and Factors."""
22

3+
from __future__ import annotations
4+
35
import json
4-
from collections.abc import Sequence
56
from functools import partial
6-
from typing import Any, Callable, Optional, Union, cast
7+
from typing import TYPE_CHECKING, Any, Callable, cast
78

89
import aiohttp
910
import aiohttp.http
1011
import requests
1112
import requests.utils
12-
from requests.models import Response
13+
from typing_extensions import Self
1314

1415
from .errors import (
1516
AuthenticationError,
@@ -23,6 +24,12 @@
2324
from .request import prepare_report, prepare_transaction
2425
from .version import __version__
2526

27+
if TYPE_CHECKING:
28+
import types
29+
from collections.abc import Sequence
30+
31+
from requests.models import Response
32+
2633
_AIOHTTP_UA = f"minFraud-API/{__version__} {aiohttp.http.SERVER_SOFTWARE}"
2734

2835
_REQUEST_UA = f"minFraud-API/{__version__} {requests.utils.default_user_agent()}"
@@ -68,32 +75,35 @@ def _handle_success(
6875
raw_body: str,
6976
uri: str,
7077
model_class: Callable,
71-
) -> Union[Score, Factors, Insights]:
78+
) -> Score | Factors | Insights:
7279
"""Handle successful response."""
7380
try:
7481
decoded_body = json.loads(raw_body)
7582
except ValueError as ex:
76-
raise MinFraudError(
83+
msg = (
7784
"Received a 200 response but could not decode the "
78-
f"response as JSON: {raw_body}",
85+
f"response as JSON: {raw_body}"
86+
)
87+
raise MinFraudError(
88+
msg,
7989
200,
8090
uri,
8191
) from ex
82-
return model_class(**decoded_body) # type: ignore
92+
return model_class(**decoded_body)
8393

8494
def _exception_for_error(
8595
self,
8696
status: int,
87-
content_type: Optional[str],
97+
content_type: str | None,
8898
raw_body: str,
8999
uri: str,
90-
) -> Union[
91-
AuthenticationError,
92-
InsufficientFundsError,
93-
InvalidRequestError,
94-
HTTPError,
95-
PermissionRequiredError,
96-
]:
100+
) -> (
101+
AuthenticationError
102+
| InsufficientFundsError
103+
| InvalidRequestError
104+
| HTTPError
105+
| PermissionRequiredError
106+
):
97107
"""Return the exception for the error responses."""
98108
if 400 <= status < 500:
99109
return self._exception_for_4xx_status(status, content_type, raw_body, uri)
@@ -104,16 +114,16 @@ def _exception_for_error(
104114
def _exception_for_4xx_status(
105115
self,
106116
status: int,
107-
content_type: Optional[str],
117+
content_type: str | None,
108118
raw_body: str,
109119
uri: str,
110-
) -> Union[
111-
AuthenticationError,
112-
InsufficientFundsError,
113-
InvalidRequestError,
114-
HTTPError,
115-
PermissionRequiredError,
116-
]:
120+
) -> (
121+
AuthenticationError
122+
| InsufficientFundsError
123+
| InvalidRequestError
124+
| HTTPError
125+
| PermissionRequiredError
126+
):
117127
"""Return exception for error responses with 4xx status codes."""
118128
if not raw_body:
119129
return HTTPError(
@@ -161,12 +171,12 @@ def _exception_for_web_service_error(
161171
code: str,
162172
status: int,
163173
uri: str,
164-
) -> Union[
165-
InvalidRequestError,
166-
AuthenticationError,
167-
PermissionRequiredError,
168-
InsufficientFundsError,
169-
]:
174+
) -> (
175+
InvalidRequestError
176+
| AuthenticationError
177+
| PermissionRequiredError
178+
| InsufficientFundsError
179+
):
170180
"""Return exception for error responses with the JSON body."""
171181
if code in (
172182
"ACCOUNT_ID_REQUIRED",
@@ -185,7 +195,7 @@ def _exception_for_web_service_error(
185195
@staticmethod
186196
def _exception_for_5xx_status(
187197
status: int,
188-
raw_body: Optional[str],
198+
raw_body: str | None,
189199
uri: str,
190200
) -> HTTPError:
191201
"""Return exception for error response with 5xx status codes."""
@@ -199,7 +209,7 @@ def _exception_for_5xx_status(
199209
@staticmethod
200210
def _exception_for_unexpected_status(
201211
status: int,
202-
raw_body: Optional[str],
212+
raw_body: str | None,
203213
uri: str,
204214
) -> HTTPError:
205215
"""Return exception for responses with unexpected status codes."""
@@ -215,7 +225,7 @@ class AsyncClient(BaseClient):
215225
"""Async client for accessing the minFraud web services."""
216226

217227
_existing_session: aiohttp.ClientSession
218-
_proxy: Optional[str]
228+
_proxy: str | None
219229

220230
def __init__(
221231
self,
@@ -224,7 +234,7 @@ def __init__(
224234
host: str = "minfraud.maxmind.com",
225235
locales: Sequence[str] = ("en",),
226236
timeout: float = 60,
227-
proxy: Optional[str] = None,
237+
proxy: str | None = None,
228238
) -> None:
229239
"""Construct AsyncClient.
230240
@@ -368,7 +378,7 @@ async def score(
368378

369379
async def report(
370380
self,
371-
report: dict[str, Optional[str]],
381+
report: dict[str, str | None],
372382
validate: bool = True,
373383
) -> None:
374384
"""Send a transaction report to the Report Transaction endpoint.
@@ -405,7 +415,7 @@ async def _response_for(
405415
request: dict[str, Any],
406416
validate: bool,
407417
hash_email: bool,
408-
) -> Union[Score, Factors, Insights]:
418+
) -> Score | Factors | Insights:
409419
"""Send request and create response object."""
410420
prepared_request = prepare_transaction(request, validate, hash_email)
411421
async with await self._do_request(uri, prepared_request) as response:
@@ -443,17 +453,22 @@ async def close(self) -> None:
443453
if hasattr(self, "_existing_session"):
444454
await self._existing_session.close()
445455

446-
async def __aenter__(self) -> "AsyncClient":
456+
async def __aenter__(self) -> Self:
447457
return self
448458

449-
async def __aexit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
459+
async def __aexit__(
460+
self,
461+
exc_type: type[BaseException] | None,
462+
exc_value: BaseException | None,
463+
traceback: types.TracebackType | None,
464+
) -> None:
450465
await self.close()
451466

452467

453468
class Client(BaseClient):
454469
"""Synchronous client for accessing the minFraud web services."""
455470

456-
_proxies: Optional[dict[str, str]]
471+
_proxies: dict[str, str] | None
457472
_session: requests.Session
458473

459474
def __init__(
@@ -463,7 +478,7 @@ def __init__(
463478
host: str = "minfraud.maxmind.com",
464479
locales: Sequence[str] = ("en",),
465480
timeout: float = 60,
466-
proxy: Optional[str] = None,
481+
proxy: str | None = None,
467482
) -> None:
468483
"""Construct Client.
469484
@@ -619,7 +634,7 @@ def score(
619634
),
620635
)
621636

622-
def report(self, report: dict[str, Optional[str]], validate: bool = True) -> None:
637+
def report(self, report: dict[str, str | None], validate: bool = True) -> None:
623638
"""Send a transaction report to the Report Transaction endpoint.
624639
625640
:param report: A dictionary containing the transaction report to be sent
@@ -654,7 +669,7 @@ def _response_for(
654669
request: dict[str, Any],
655670
validate: bool,
656671
hash_email: bool,
657-
) -> Union[Score, Factors, Insights]:
672+
) -> Score | Factors | Insights:
658673
"""Send request and create response object."""
659674
prepared_request = prepare_transaction(request, validate, hash_email)
660675

@@ -681,8 +696,13 @@ def close(self) -> None:
681696
"""
682697
self._session.close()
683698

684-
def __enter__(self) -> "Client":
699+
def __enter__(self) -> Self:
685700
return self
686701

687-
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None:
702+
def __exit__(
703+
self,
704+
exc_type: type[BaseException] | None,
705+
exc_value: BaseException | None,
706+
traceback: types.TracebackType | None,
707+
) -> None:
688708
self.close()

tests/test_models.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1-
import unittest
2-
from typing import Any, Union
1+
from __future__ import annotations
32

4-
from minfraud.models import *
3+
import unittest
4+
from typing import Any
5+
6+
from minfraud.models import (
7+
BillingAddress,
8+
CreditCard,
9+
Device,
10+
Disposition,
11+
Email,
12+
EmailDomain,
13+
Factors,
14+
GeoIP2Location,
15+
Insights,
16+
IPAddress,
17+
Issuer,
18+
Phone,
19+
Reason,
20+
RiskScoreReason,
21+
Score,
22+
ScoreIPAddress,
23+
ServiceWarning,
24+
ShippingAddress,
25+
)
526

627

728
class TestModels(unittest.TestCase):
@@ -23,7 +44,7 @@ def test_shipping_address(self) -> None:
2344
self.assertEqual(200, address.distance_to_billing_address)
2445

2546
@property
26-
def address_dict(self) -> dict[str, Union[bool, float]]:
47+
def address_dict(self) -> dict[str, bool | float]:
2748
return {
2849
"is_in_ip_country": True,
2950
"latitude": 43.1,
@@ -32,7 +53,7 @@ def address_dict(self) -> dict[str, Union[bool, float]]:
3253
"is_postal_in_city": True,
3354
}
3455

35-
def check_address(self, address) -> None:
56+
def check_address(self, address: BillingAddress | ShippingAddress) -> None:
3657
self.assertEqual(True, address.is_in_ip_country)
3758
self.assertEqual(True, address.is_postal_in_city)
3859
self.assertEqual(100, address.distance_to_ip_location)
@@ -374,7 +395,7 @@ def factors_response(self) -> dict[str, Any]:
374395
],
375396
}
376397

377-
def check_insights_data(self, insights, uuid) -> None:
398+
def check_insights_data(self, insights: Insights | Factors, uuid: str) -> None:
378399
self.assertEqual("US", insights.ip_address.country.iso_code)
379400
self.assertEqual(False, insights.ip_address.country.is_in_european_union)
380401
self.assertEqual(True, insights.credit_card.is_business)
@@ -396,7 +417,7 @@ def check_insights_data(self, insights, uuid) -> None:
396417
self.assertEqual("INVALID_INPUT", insights.warnings[0].code)
397418
self.assertIsInstance(insights.warnings, list, "warnings is a list")
398419

399-
def check_risk_score_reasons_data(self, reasons) -> None:
420+
def check_risk_score_reasons_data(self, reasons: list[RiskScoreReason]) -> None:
400421
self.assertEqual(1, len(reasons))
401422
self.assertEqual(45, reasons[0].multiplier)
402423
self.assertEqual(1, len(reasons[0].reasons))

0 commit comments

Comments
 (0)