Skip to content

Commit 794ba05

Browse files
committed
Add type hints for public API (#191)
Add type annotations to the public API surface: - Message, html(), BaseMessage and all mixins - SMTPBackend, Response, SMTPResponse - DKIMSigner - BaseFile, LazyHTTPFile, MemoryFileStore - MessageID and utility functions Add py.typed marker for PEP 561 compliance.
1 parent 7515f15 commit 794ba05

9 files changed

Lines changed: 266 additions & 215 deletions

File tree

emails/backend/response.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,61 @@
11
# encoding: utf-8
2+
from __future__ import annotations
23

4+
from typing import Any
35

4-
class Response(object):
56

6-
def __init__(self, exception=None, backend=None):
7+
class Response:
8+
9+
def __init__(self, exception: Exception | None = None, backend: Any = None) -> None:
710
self.backend = backend
811
self.set_exception(exception)
9-
self.from_addr = None
10-
self.to_addrs = None
11-
self._finished = False
12+
self.from_addr: str | None = None
13+
self.to_addrs: list[str] | None = None
14+
self._finished: bool = False
1215

13-
def set_exception(self, exc):
16+
def set_exception(self, exc: Exception | None) -> None:
1417
self._exc = exc
1518

16-
def raise_if_needed(self):
19+
def raise_if_needed(self) -> None:
1720
if self._exc:
1821
raise self._exc
1922

2023
@property
21-
def error(self):
24+
def error(self) -> Exception | None:
2225
return self._exc
2326

2427
@property
25-
def success(self):
28+
def success(self) -> bool:
2629
return self._finished
2730

2831

2932
class SMTPResponse(Response):
3033

31-
def __init__(self, exception=None, backend=None):
34+
def __init__(self, exception: Exception | None = None, backend: Any = None) -> None:
3235

3336
super(SMTPResponse, self).__init__(exception=exception, backend=backend)
3437

35-
self.responses = []
38+
self.responses: list[list] = []
3639

37-
self.esmtp_opts = None
38-
self.rcpt_options = None
40+
self.esmtp_opts: str | None = None
41+
self.rcpt_options: str | None = None
3942

40-
self.status_code = None
41-
self.status_text = None
42-
self.last_command = None
43-
self.refused_recipient = {}
43+
self.status_code: int | None = None
44+
self.status_text: str | None = None
45+
self.last_command: str | None = None
46+
self.refused_recipient: dict[str, tuple[int, str]] = {}
4447

45-
def set_status(self, command, code, text, **kwargs):
48+
def set_status(self, command: str, code: int, text: str, **kwargs: Any) -> None:
4649
self.responses.append([command, code, text, kwargs])
4750
self.status_code = code
4851
self.status_text = text
4952
self.last_command = command
5053

5154
@property
52-
def success(self):
53-
return self._finished and self.status_code and self.status_code == 250
55+
def success(self) -> bool:
56+
return self._finished and self.status_code is not None and self.status_code == 250
5457

55-
def __repr__(self):
58+
def __repr__(self) -> str:
5659
return "<emails.backend.SMTPResponse status_code=%s status_text=%s>" % (self.status_code.__repr__(),
5760
self.status_text.__repr__())
5861

emails/backend/smtp/backend.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
# encoding: utf-8
2-
import smtplib
2+
from __future__ import annotations
3+
34
import logging
5+
import smtplib
6+
from collections.abc import Callable
47
from functools import wraps
8+
from types import TracebackType
9+
from typing import Any
10+
511
from ..response import SMTPResponse
612
from .client import SMTPClientWithResponse, SMTPClientWithResponse_SSL
713
from ...utils import DNS_NAME
@@ -13,7 +19,7 @@
1319
logger = logging.getLogger(__name__)
1420

1521

16-
class SMTPBackend(object):
22+
class SMTPBackend:
1723

1824
"""
1925
SMTPBackend manages a smtp connection.
@@ -25,7 +31,8 @@ class SMTPBackend(object):
2531
connection_ssl_cls = SMTPClientWithResponse_SSL
2632
response_cls = SMTPResponse
2733

28-
def __init__(self, ssl=False, fail_silently=True, mail_options=None, **kwargs):
34+
def __init__(self, ssl: bool = False, fail_silently: bool = True,
35+
mail_options: list[str] | None = None, **kwargs: Any) -> None:
2936

3037
self.smtp_cls = ssl and self.connection_ssl_cls or self.connection_cls
3138

@@ -42,19 +49,19 @@ def __init__(self, ssl=False, fail_silently=True, mail_options=None, **kwargs):
4249

4350
self.smtp_cls_kwargs = kwargs
4451

45-
self.host = kwargs.get('host')
46-
self.port = kwargs.get('port')
52+
self.host: str | None = kwargs.get('host')
53+
self.port: int = kwargs.get('port')
4754
self.fail_silently = fail_silently
4855
self.mail_options = mail_options or []
4956

50-
self._client = None
57+
self._client: SMTPClientWithResponse | None = None
5158

52-
def get_client(self):
59+
def get_client(self) -> SMTPClientWithResponse:
5360
if self._client is None:
5461
self._client = self.smtp_cls(parent=self, **self.smtp_cls_kwargs)
5562
return self._client
5663

57-
def close(self):
64+
def close(self) -> None:
5865

5966
"""
6067
Closes the connection to the email server.
@@ -70,12 +77,12 @@ def close(self):
7077
finally:
7178
self._client = None
7279

73-
def make_response(self, exception=None):
80+
def make_response(self, exception: Exception | None = None) -> SMTPResponse:
7481
return self.response_cls(backend=self, exception=exception)
7582

76-
def retry_on_disconnect(self, func):
83+
def retry_on_disconnect(self, func: Callable[..., SMTPResponse]) -> Callable[..., SMTPResponse]:
7784
@wraps(func)
78-
def wrapper(*args, **kwargs):
85+
def wrapper(*args: Any, **kwargs: Any) -> SMTPResponse:
7986
try:
8087
return func(*args, **kwargs)
8188
except smtplib.SMTPServerDisconnected:
@@ -85,7 +92,7 @@ def wrapper(*args, **kwargs):
8592
return func(*args, **kwargs)
8693
return wrapper
8794

88-
def _send(self, **kwargs):
95+
def _send(self, **kwargs: Any) -> SMTPResponse:
8996

9097
response = None
9198
try:
@@ -106,7 +113,9 @@ def _send(self, **kwargs):
106113
else:
107114
return client.sendmail(**kwargs)
108115

109-
def sendmail(self, from_addr, to_addrs, msg, mail_options=None, rcpt_options=None):
116+
def sendmail(self, from_addr: str, to_addrs: str | list[str],
117+
msg: Any, mail_options: list[str] | None = None,
118+
rcpt_options: list[str] | None = None) -> SMTPResponse | None:
110119

111120
if not to_addrs:
112121
return None
@@ -127,8 +136,10 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=None, rcpt_options=Non
127136

128137
return response
129138

130-
def __enter__(self):
139+
def __enter__(self) -> SMTPBackend:
131140
return self
132141

133-
def __exit__(self, exc_type, exc_value, traceback):
142+
def __exit__(self, exc_type: type[BaseException] | None,
143+
exc_value: BaseException | None,
144+
traceback: TracebackType | None) -> None:
134145
self.close()

emails/exc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# encoding: utf-8
2+
from __future__ import annotations
23

34
from .packages.dkim import DKIMException
45

0 commit comments

Comments
 (0)