11import re
22import warnings
3- from typing import Optional
3+ from typing import Optional , Union
44
55from django .core import mail
66from django .core .mail import EmailMultiAlternatives
1010class 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
155149class 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