Skip to content

Commit 5f9c8ea

Browse files
authored
#30: Replaced docstring type hints with proper Pythonic ones in test suit (#31)
1 parent 439e6e6 commit 5f9c8ea

File tree

3 files changed

+55
-65
lines changed

3 files changed

+55
-65
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
**2.6.3** (2025-05-09)
4+
* Replaced docstring type hints with proper Pythonic ones in test suit
5+
36
**2.6.2** (2025-04-03)
47
* Maintenance updates via ambient-package-update
58

django_pony_express/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Class-based emails including a test suite for Django"""
22

3-
__version__ = "2.6.2"
3+
__version__ = "2.6.3"
Lines changed: 51 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22
import warnings
3-
from typing import Optional
3+
from typing import Optional, Union
44

55
from django.core import mail
66
from django.core.mail import EmailMultiAlternatives
@@ -10,38 +10,38 @@
1010
class EmailTestService:
1111
_outbox = None
1212

13-
def _ensure_outbox_is_loaded(self):
13+
def _ensure_outbox_is_loaded(self) -> None:
1414
"""
1515
Ensures that the outbox attribute is set
1616
"""
1717
if self._outbox is None:
1818
self.reload()
1919

20-
def reload(self):
20+
def reload(self) -> None:
2121
"""
2222
Loads the current _outbox inside an attribute of this class
2323
"""
2424
self._outbox = mail.outbox
2525

26-
def empty(self):
26+
def empty(self) -> None:
2727
"""
2828
Empties the current outbox
29-
:return:
3029
"""
3130
mail.outbox = []
3231
self.reload()
3332

34-
def filter(self, to=None, cc=None, bcc=None, subject=None):
33+
def filter(
34+
self,
35+
to: Optional[str] = None,
36+
cc: Optional[str] = None,
37+
bcc: Optional[str] = None,
38+
subject: Union[str, re.Pattern, None] = None,
39+
) -> "EmailTestServiceQuerySet":
3540
"""
3641
Searches in the _outbox for emails matching either to and/or subject.
3742
Returns a list of email objects
38-
:param to: str
39-
:param cc: str
40-
:param bcc: str
41-
:param subject: str | re.Pattern
42-
:return: EmailTestService
4343
"""
44-
# Ensure that outbox is up-to-date
44+
# Ensure that outbox is up to date
4545
self.reload()
4646

4747
if not any([to, cc, bcc, subject]):
@@ -70,12 +70,11 @@ def filter(self, to=None, cc=None, bcc=None, subject=None):
7070

7171
return EmailTestServiceQuerySet(matching_list=match_list)
7272

73-
def all(self):
73+
def all(self) -> "EmailTestServiceQuerySet":
7474
"""
7575
Loads all mails from the outbox inside the matching list
76-
:return:
7776
"""
78-
# Ensure that outbox is up-to-date
77+
# Ensure that outbox is up to date
7978
self.reload()
8079

8180
# Load data to matching list
@@ -91,39 +90,33 @@ class EmailTestServiceMail(mail.EmailMultiAlternatives):
9190

9291
_testcase = TestCase() # Hacky way to get access to TestCase.assert* methods without deriving from TestCase
9392

94-
def _get_html_content(self):
93+
def _get_html_content(self) -> Optional[str]:
9594
"""
96-
Ensure we just have found one element and then return HTML part of the email
97-
:return: str
95+
Ensure we just have found one element and then return the HTML part of the email
9896
"""
9997
# Search for string
10098
if len(self.alternatives) > 0:
10199
return self.alternatives[0][0]
102100
return None
103101

104-
def _get_txt_content(self):
102+
def _get_txt_content(self) -> str:
105103
"""
106-
Ensure we just have found one element and then return text part of the email
107-
:return: str
104+
Ensure we just have found one element and then return the text part of the email
108105
"""
109106
# Search for string
110107
return self.body
111108

112-
def assert_subject(self, subject, msg=None):
109+
def assert_subject(self, subject: str, msg: Optional[str] = None) -> None:
113110
"""
114111
Searches in a given email inside the HTML AND TXT part for a given string
115-
:param subject: str
116-
:param msg: str
117112
"""
118113

119-
# Assert expected subject is equal to the generated one
114+
# Assert the expected subject is equal to the generated one
120115
self._testcase.assertEqual(subject, self.subject, msg=msg)
121116

122-
def assert_body_contains(self, search_str, msg=None):
117+
def assert_body_contains(self, search_str: str, msg: Optional[str] = None) -> None:
123118
"""
124119
Searches in a given email inside the HTML AND TXT part for a given string
125-
:param search_str: str
126-
:param msg: str
127120
"""
128121

129122
# TODO: use Django 5.2 `body_contains()` once we drop older versions
@@ -135,56 +128,55 @@ def assert_body_contains(self, search_str, msg=None):
135128
if html_content is not None:
136129
self._testcase.assertIn(search_str, html_content, msg=msg)
137130

138-
def assert_body_contains_not(self, search_str, msg=None):
131+
def assert_body_contains_not(self, search_str: str, msg: Optional[str] = None) -> None:
139132
"""
140133
Searches in a given email inside the HTML AND TXT part for a given string
141-
:param search_str: str
142-
:param msg: str
143134
"""
144135

145-
# Assert string is contained in HTML part
136+
# Assert string is contained in the HTML part
146137
self._testcase.assertNotIn(search_str, self._get_html_content(), msg=msg)
147-
# Assert string is contained in TXT part
138+
# Assert string is contained in the TXT part
148139
self._testcase.assertNotIn(search_str, self._get_txt_content(), msg=msg)
149140

150-
def assert_to_contains(self, *emails: list[str]):
141+
def assert_to_contains(self, *emails: list[str]) -> None:
142+
"""
143+
Searches in all given email for a specific email address in the "to"
144+
"""
151145
for email in emails:
152146
self._testcase.assertIn(email, self.to)
153147

154148

155149
class EmailTestServiceQuerySet(TestCase):
156150
_match_list = None
157151

158-
def __init__(self, matching_list=None):
152+
def __init__(self, matching_list: Optional[list] = None) -> None:
159153
super().__init__()
160154
self._match_list = matching_list
161155
for email in self._match_list or []:
162-
# Change the class of every EmailMultiAlternative instance, so that it points to
156+
# Change the class of every EmailMultiAlternative instance so that it points to
163157
# our subclass, which has some additional assertion-methods.
164158
if isinstance(email, EmailMultiAlternatives):
165159
email.__class__ = EmailTestServiceMail
166160

167-
def _get_html_content(self):
161+
def _get_html_content(self) -> Optional[str]:
168162
"""
169-
Ensure we just have found one element and then return HTML part of the email
170-
:return: str
163+
Ensure we just have found one element and then return the HTML part of the email
171164
"""
172165
self._validate_lookup_cache_contains_one_element()
173166
# Search for string
174167
if len(self[0].alternatives) > 0:
175168
return self[0].alternatives[0][0]
176169
return None
177170

178-
def _get_txt_content(self):
171+
def _get_txt_content(self) -> str:
179172
"""
180-
Ensure we just have found one element and then return text part of the email
181-
:return: str
173+
Ensure we just have found one element and then return the text part of the email
182174
"""
183175
# Search for string
184176
self._validate_lookup_cache_contains_one_element()
185177
return self[0].body
186178

187-
def _validate_lookup_cache_contains_one_element(self):
179+
def _validate_lookup_cache_contains_one_element(self) -> None:
188180
"""
189181
Ensures that in the cached lookup is exactly one element. Needed for full-text-search.
190182
"""
@@ -193,7 +185,7 @@ def _validate_lookup_cache_contains_one_element(self):
193185
elif self.count() == 0:
194186
raise RuntimeError("Current lookup has zero matches so lookup does not make sense.")
195187

196-
def _ensure_matching_list_was_populated(self):
188+
def _ensure_matching_list_was_populated(self) -> None:
197189
"""
198190
Make sure that we queried at least once before working with the results
199191
"""
@@ -202,56 +194,51 @@ def _ensure_matching_list_was_populated(self):
202194
"Counting of matches called without previous query. Please call filter() or all() first."
203195
)
204196

205-
def one(self):
197+
def one(self) -> bool:
206198
"""
207199
Checks if the previous query returned exactly one element
208-
:return: bool
209200
"""
210201
return self.count() == 1
211202

212-
def count(self):
203+
def count(self) -> int:
213204
"""
214205
Returns the number of matches found by a previous call of `find()`
215-
:return: int
216206
"""
217-
# Ensure is was queried before using results
207+
# Ensure list was queried before using results
218208
self._ensure_matching_list_was_populated()
219209

220210
# Count matches
221211
return len(self)
222212

223-
def first(self):
213+
def first(self) -> EmailMultiAlternatives:
224214
"""
225215
Returns the first found element
226-
:return: EmailMultiAlternatives
227216
"""
228-
# Ensure is was queried before using results
217+
# Ensure list was queried before using results
229218
self._ensure_matching_list_was_populated()
230219
return self[0] if self.count() > 0 else False
231220

232-
def last(self):
221+
def last(self) -> EmailMultiAlternatives:
233222
"""
234223
Returns the last found element
235-
:return: EmailMultiAlternatives
236224
"""
237-
# Ensure is was queried before using results
225+
# Ensure list was queried before using results
238226
self._ensure_matching_list_was_populated()
239227
return self[-1] if self.count() > 0 else False
240228

241229
def assert_one(self, msg: Optional[str] = None):
242230
"""
243-
Makes an assertion to make sure queried element exists exactly once
231+
Makes an assertion to make sure the queried element exists exactly once
244232
"""
245233
self.assertEqual(self.one(), True, msg=msg)
246234

247-
def assert_quantity(self, target_quantity: str, msg: Optional[str] = None):
235+
def assert_quantity(self, target_quantity: str, msg: Optional[str] = None) -> None:
248236
"""
249-
Makes an assertion to make sure that amount of queried mails are equal to `target_quantity`
250-
:return:
237+
Makes an assertion to make sure that the number of queried mails is equal to `target_quantity`
251238
"""
252239
self.assertEqual(self.count(), target_quantity, msg=msg)
253240

254-
def assert_subject(self, subject: str, msg: Optional[str] = None):
241+
def assert_subject(self, subject: str, msg: Optional[str] = None) -> None:
255242
"""
256243
Searches in a given email inside the HTML AND TXT part for a given string
257244
"""
@@ -263,7 +250,7 @@ def assert_subject(self, subject: str, msg: Optional[str] = None):
263250
self._validate_lookup_cache_contains_one_element()
264251
self[0].assert_subject(subject, msg)
265252

266-
def assert_body_contains(self, search_str: str, msg: Optional[str] = None):
253+
def assert_body_contains(self, search_str: str, msg: Optional[str] = None) -> None:
267254
"""
268255
Searches in a given email inside the HTML AND TXT part for a given string
269256
"""
@@ -276,7 +263,7 @@ def assert_body_contains(self, search_str: str, msg: Optional[str] = None):
276263
self._validate_lookup_cache_contains_one_element()
277264
self[0].assert_body_contains(search_str, msg)
278265

279-
def assert_body_contains_not(self, search_str: str, msg: Optional[str] = None):
266+
def assert_body_contains_not(self, search_str: str, msg: Optional[str] = None) -> None:
280267
"""
281268
Searches in a given email inside the HTML AND TXT part for a given string
282269
"""
@@ -289,8 +276,8 @@ def assert_body_contains_not(self, search_str: str, msg: Optional[str] = None):
289276
self._validate_lookup_cache_contains_one_element()
290277
self[0].assert_body_contains_not(search_str, msg)
291278

292-
def __getitem__(self, item):
279+
def __getitem__(self, item) -> EmailTestServiceMail:
293280
return self._match_list.__getitem__(item)
294281

295-
def __len__(self):
282+
def __len__(self) -> int:
296283
return self._match_list.__len__()

0 commit comments

Comments
 (0)