Skip to content

Commit ef330c6

Browse files
check typing with mypy
This change adds tox configuration to run mypy, and updates the type information so test passes. --- Co-Authored-By: Ian Campbell <ipcampbell@gmail.com>
1 parent f460b58 commit ef330c6

117 files changed

Lines changed: 420 additions & 90 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

anymail/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from ._version import VERSION, __version__
24

35
__all__ = [

anymail/_idna.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Pre-packaged IDNA_ENCODER options
2+
from __future__ import annotations
3+
24
import idna
35

46
from anymail.exceptions import AnymailImproperlyInstalled, _LazyError

anymail/_version.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Don't import this file directly (unless you are a build system).
22
# Instead, load version info from the package root.
33

4+
from __future__ import annotations
5+
46
#: major.minor or major.minor.patch (optionally with .devN suffix)
57
__version__ = "14.0"
68

anymail/apps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from django.apps import AppConfig
24
from django.core import checks
35

anymail/backends/amazon_ses.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from __future__ import annotations
2+
13
import email.charset
24
import email.encoders
35
import email.policy
6+
from typing import Any
47

58
from requests.structures import CaseInsensitiveDict
69

@@ -165,6 +168,7 @@ def init_payload(self):
165168
try:
166169
for field in address_fields:
167170
addresses = getattr(self.message, field)
171+
idna_encoded_addresses: str | list[str]
168172
idna_encoded_addresses = [
169173
address.format(idna_encode=self.backend.idna_encode)
170174
for address in parse_address_list(addresses, field)
@@ -364,7 +368,7 @@ class AmazonSESV2SendBulkEmailPayload(AmazonSESBasePayload):
364368
def init_payload(self):
365369
super().init_payload()
366370
# late-bind in finalize_payload:
367-
self.recipients = {"to": [], "cc": [], "bcc": []}
371+
self.recipients = dict[str, list[Any]](to=[], cc=[], bcc=[])
368372
self.merge_data = {}
369373
self.headers = {}
370374
self.merge_headers = {}

anymail/backends/base.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from __future__ import annotations
2+
13
import json
24
from datetime import date, datetime, timezone
5+
from typing import Any
36

47
from django.core.mail.backends.base import BaseEmailBackend
58
from django.utils.module_loading import import_string
@@ -20,6 +23,7 @@
2023
from ..utils import (
2124
UNSET,
2225
Attachment,
26+
UnsetType,
2327
concat_lists,
2428
force_non_lazy,
2529
force_non_lazy_dict,
@@ -334,7 +338,9 @@ def __init__(self, message, defaults, backend):
334338
self.defaults = defaults
335339
self.backend = backend
336340
self.esp_name = backend.esp_name
337-
self._batch_attrs_used = {attr: UNSET for attr in self.batch_attrs}
341+
self._batch_attrs_used: dict[str, Any | UnsetType] = {
342+
attr: UNSET for attr in self.batch_attrs
343+
}
338344

339345
self.init_payload()
340346

@@ -358,7 +364,7 @@ def __init__(self, message, defaults, backend):
358364
converter = getattr(self, converter)
359365
if converter in (parse_address_list, parse_single_address):
360366
# hack to include field name in error message
361-
value = converter(value, field=attr)
367+
value = converter(value, field=attr) # type: ignore[call-arg]
362368
else:
363369
value = converter(value)
364370
if value is not UNSET:
@@ -663,7 +669,7 @@ def set_esp_extra(self, extra):
663669
# Helpers for concrete implementations
664670
#
665671

666-
def serialize_json(self, data):
672+
def serialize_json(self, data) -> str:
667673
"""Returns data serialized to json, raising appropriate errors.
668674
669675
Essentially json.dumps with added context in any errors.

anymail/backends/base_requests.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from __future__ import annotations
2+
3+
from typing import cast
14
from urllib.parse import urljoin
25

36
import requests
@@ -10,6 +13,8 @@
1013

1114

1215
class AnymailRequestsBackend(AnymailBaseBackend):
16+
session: requests.Session | None
17+
1318
"""
1419
Base Anymail email backend for ESPs that use an HTTP API via requests
1520
"""
@@ -73,7 +78,7 @@ def create_session(self):
7378
session.headers["User-Agent"] = "django-anymail/{version}-{esp} {orig}".format(
7479
esp=self.esp_name.lower(),
7580
version=__version__,
76-
orig=session.headers.get("User-Agent", ""),
81+
orig=cast(str, session.headers.get("User-Agent", "")),
7782
)
7883

7984
if self.debug_api_requests:
@@ -93,6 +98,7 @@ def post_to_esp(self, payload, message):
9398
params = payload.get_request_params(self.api_url)
9499
params.setdefault("timeout", self.timeout)
95100
try:
101+
assert self.session
96102
response = self.session.request(**params)
97103
except requests.RequestException as err:
98104
# raise an exception that is both AnymailRequestsAPIError

anymail/backends/brevo.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Literal
4+
15
from requests.structures import CaseInsensitiveDict
26

37
from ..exceptions import AnymailRequestsAPIError
@@ -103,7 +107,7 @@ def __init__(self, message, defaults, backend, *args, **kwargs):
103107
http_headers["api-key"] = backend.api_key
104108
http_headers["Content-Type"] = "application/json"
105109

106-
super().__init__(
110+
super().__init__( # type: ignore[misc]
107111
message, defaults, backend, headers=http_headers, *args, **kwargs
108112
)
109113

@@ -125,8 +129,8 @@ def serialize_data(self):
125129
self.data["messageVersions"] = []
126130
for to in to_list:
127131
to_email = to["email"]
128-
version = {"to": [to]}
129-
headers = CaseInsensitiveDict()
132+
version = dict[str, Any](to=[to])
133+
headers = CaseInsensitiveDict[str]()
130134
if to_email in self.merge_data:
131135
version["params"] = self.merge_data[to_email]
132136
if to_email in self.merge_metadata:
@@ -158,7 +162,7 @@ def serialize_data(self):
158162

159163
def email_object(self, email):
160164
"""Converts EmailAddress to Brevo API object with workarounds"""
161-
use_rfc2047 = False
165+
use_rfc2047: bool | Literal["force"] = False
162166
# In from/to/cc/bcc, Brevo silently drops the "name" field if it
163167
# contains both non-ASCII chars and a comma (or other special).
164168
# In replyTo, Brevo sends the name as 8-bit (unencoded) utf-8.

anymail/backends/console.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import uuid
24

35
from django.core.mail.backends.console import EmailBackend as DjangoConsoleBackend

anymail/backends/mailersend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import mimetypes
24

35
from ..exceptions import AnymailRequestsAPIError, AnymailUnsupportedFeature
@@ -127,7 +129,7 @@ def __init__(self, message, defaults, backend, *args, **kwargs):
127129
self.merge_data = {} # late bound
128130
self.merge_global_data = None # late bound
129131
self.batch_send_mode = backend.batch_send_mode # can override in esp_extra
130-
super().__init__(message, defaults, backend, headers=headers, *args, **kwargs)
132+
super().__init__(message, defaults, backend, headers=headers, *args, **kwargs) # type: ignore[misc] # noqa: E501
131133

132134
def get_api_endpoint(self):
133135
if self.is_batch():

0 commit comments

Comments
 (0)