Browse Source

Clean code of tracim/tracim/lib/email_fetcher.py

Bastien Sevajol 7 years ago
parent
commit
70bd70f867

+ 2 - 1
tracim/tracim/controllers/events.py View File

@@ -1,4 +1,5 @@
1 1
 import tg
2
+import typing
2 3
 from tg import request
3 4
 from tg import Response
4 5
 from tg import abort
@@ -14,7 +15,7 @@ from tracim.config.app_cfg import CFG
14 15
 class EventRestController(RestController):
15 16
 
16 17
     @tg.expose('json')
17
-    def post(self):
18
+    def post(self) -> Response:
18 19
         cfg = CFG.get_instance()
19 20
 
20 21
         try:

+ 4 - 4
tracim/tracim/lib/daemons.py View File

@@ -158,12 +158,12 @@ class MailFetcherDaemon(Daemon):
158 158
     send http request to a tracim endpoint to handle them.
159 159
     """
160 160
 
161
-    def __init__(self, *args, **kwargs):
161
+    def __init__(self, *args, **kwargs) -> None:
162 162
         super().__init__(*args, **kwargs)
163
-        self._fetcher = None
163
+        self._fetcher = None  # type: MailFetcher
164 164
         self.ok = True
165 165
 
166
-    def run(self):
166
+    def run(self) -> None:
167 167
         from tracim.config.app_cfg import CFG
168 168
         cfg = CFG.get_instance()
169 169
         self._fetcher = MailFetcher(
@@ -180,7 +180,7 @@ class MailFetcherDaemon(Daemon):
180 180
         )
181 181
         self._fetcher.run()
182 182
 
183
-    def stop(self):
183
+    def stop(self) -> None:
184 184
         if self._fetcher:
185 185
             self._fetcher.stop()
186 186
 

+ 45 - 34
tracim/tracim/lib/email_fetcher.py View File

@@ -13,7 +13,7 @@ from email import message_from_bytes
13 13
 
14 14
 import markdown
15 15
 import requests
16
-from bs4 import BeautifulSoup
16
+from bs4 import BeautifulSoup, Tag
17 17
 from email_reply_parser import EmailReplyParser
18 18
 
19 19
 from tracim.lib.base import logger
@@ -35,27 +35,26 @@ CONTENT_TYPE_TEXT_HTML = 'text/html'
35 35
 
36 36
 
37 37
 class DecodedMail(object):
38
-
39
-    def __init__(self, message: Message):
38
+    def __init__(self, message: Message) -> None:
40 39
         self._message = message
41 40
 
42 41
     def _decode_header(self, header_title: str) -> typing.Optional[str]:
43 42
         # FIXME : Handle exception
44 43
         if header_title in self._message:
45
-            return str(make_header(decode_header(header)))
44
+            return str(make_header(decode_header(self._message[header_title])))
46 45
         else:
47 46
             return None
48 47
 
49 48
     def get_subject(self) -> typing.Optional[str]:
50 49
         return self._decode_header('subject')
51 50
 
52
-    def get_from_address(self) -> typing.Optional[str]:
51
+    def get_from_address(self) -> str:
53 52
         return parseaddr(self._message['From'])[1]
54 53
 
55
-    def get_to_address(self)-> typing.Optional[str]:
54
+    def get_to_address(self) -> str:
56 55
         return parseaddr(self._message['To'])[1]
57 56
 
58
-    def get_first_ref(self) -> typing.Optional[str]:
57
+    def get_first_ref(self) -> str:
59 58
         return parseaddr(self._message['References'])[1]
60 59
 
61 60
     def get_special_key(self) -> typing.Optional[str]:
@@ -80,14 +79,14 @@ class DecodedMail(object):
80 79
         return body
81 80
 
82 81
     @classmethod
83
-    def _parse_txt_body(cls, txt_body: str):
82
+    def _parse_txt_body(cls, txt_body: str) -> str:
84 83
         txt_body = EmailReplyParser.parse_reply(txt_body)
85 84
         html_body = markdown.markdown(txt_body)
86 85
         body = DecodedMail._parse_html_body(html_body)
87 86
         return body
88 87
 
89 88
     @classmethod
90
-    def _parse_html_body(cls, html_body: str):
89
+    def _parse_html_body(cls, html_body: str) -> str:
91 90
         soup = BeautifulSoup(html_body, 'html.parser')
92 91
         config = BEAUTIFULSOUP_HTML_BODY_PARSE_CONFIG
93 92
         for tag in soup.findAll():
@@ -103,7 +102,7 @@ class DecodedMail(object):
103 102
         return str(soup)
104 103
 
105 104
     @classmethod
106
-    def _tag_to_extract(cls, tag) -> bool:
105
+    def _tag_to_extract(cls, tag: Tag) -> bool:
107 106
         config = BEAUTIFULSOUP_HTML_BODY_PARSE_CONFIG
108 107
         if tag.name.lower() in config['tag_blacklist']:
109 108
             return True
@@ -154,9 +153,13 @@ class DecodedMail(object):
154 153
         if first_ref:
155 154
             return DecodedMail.find_key_from_mail_address(first_ref)
156 155
 
156
+        return None
157
+
157 158
     @classmethod
158
-    def find_key_from_mail_address(cls, mail_address: str) \
159
-            -> typing.Optional[str]:
159
+    def find_key_from_mail_address(
160
+        cls,
161
+        mail_address: str,
162
+    ) -> typing.Optional[str]:
160 163
         """ Parse mail_adress-like string
161 164
         to retrieve key.
162 165
 
@@ -171,18 +174,18 @@ class DecodedMail(object):
171 174
 
172 175
 
173 176
 class MailFetcher(object):
174
-
175
-    def __init__(self,
176
-                 host: str,
177
-                 port: str,
178
-                 user: str,
179
-                 password: str,
180
-                 use_ssl: bool,
181
-                 folder: str,
182
-                 delay: int,
183
-                 endpoint: str,
184
-                 token: str) \
185
-            -> None:
177
+    def __init__(
178
+        self,
179
+        host: str,
180
+        port: str,
181
+        user: str,
182
+        password: str,
183
+        use_ssl: bool,
184
+        folder: str,
185
+        delay: int,
186
+        endpoint: str,
187
+        token: str,
188
+    ) -> None:
186 189
         """
187 190
         Fetch mail from a mailbox folder through IMAP and add their content to
188 191
         Tracim through http according to mail Headers.
@@ -255,7 +258,7 @@ class MailFetcher(object):
255 258
             self._connection.logout()
256 259
             self._connection = None
257 260
 
258
-    def _fetch(self) -> list:
261
+    def _fetch(self) -> typing.List[Message]:
259 262
         """
260 263
         Get news message from mailbox
261 264
         :return: list of new mails
@@ -266,16 +269,16 @@ class MailFetcher(object):
266 269
         if rv == 'OK':
267 270
             # get mails
268 271
             # TODO - G.M -  2017-11-15 Which files to select as new file ?
269
-            # Unseen file or All file from a directory (old one should be moved/
270
-            # deleted from mailbox during this process) ?
272
+            # Unseen file or All file from a directory (old one should be
273
+            #  moved/ deleted from mailbox during this process) ?
271 274
             rv, data = self._connection.search(None, "(UNSEEN)")
272 275
             if rv == 'OK':
273 276
                 # get mail content
274 277
                 for num in data[0].split():
275
-                    # INFO - G.M - 2017-11-23 - Fetch (RFC288) to retrieve all complete mails
276
-                    # see example : https://docs.python.org/fr/3.5/library/imaplib.html#imap4-example .
277
-                    # Be careful, This method remove also mails from Unseen mails
278
-
278
+                    # INFO - G.M - 2017-11-23 - Fetch (RFC288) to retrieve all
279
+                    # complete mails see example : https://docs.python.org/fr/3.5/library/imaplib.html#imap4-example .  # nopep8
280
+                    # Be careful, This method remove also mails from Unseen
281
+                    # mails
279 282
                     rv, data = self._connection.fetch(num, '(RFC822)')
280 283
                     if rv == 'OK':
281 284
                         msg = message_from_bytes(data[0][1])
@@ -291,13 +294,20 @@ class MailFetcher(object):
291 294
             logger.debug(self, log.format(str(rv)))
292 295
         return messages
293 296
 
294
-    def _notify_tracim(self, mails: list) -> list:
297
+    def _notify_tracim(
298
+        self,
299
+        mails: typing.List[DecodedMail],
300
+    ) -> typing.List[DecodedMail]:
295 301
         """
296 302
         Send http request to tracim endpoint
297 303
         :param mails: list of mails to send
298 304
         :return: unsended mails
299 305
         """
300 306
         unsended_mails = []
307
+        # TODO BS 20171124: Look around mail.get_from_address(), mail.get_key()
308
+        # , mail.get_body() etc ... for raise InvalidEmailError if missing
309
+        #  required informations (actually get_from_address raise IndexError
310
+        #  if no from address for example) and catch it here
301 311
         while mails:
302 312
             mail = mails.pop()
303 313
             msg = {'token': self.token,
@@ -312,13 +322,14 @@ class MailFetcher(object):
312 322
                     log = 'bad status code response when sending mail to tracim: {}'  # nopep8
313 323
                     logger.error(self, log.format(str(r.status_code)))
314 324
             # TODO - G.M - Verify exception correctly works
315
-            except requests.exceptions.Timeout:
325
+            except requests.exceptions.Timeout as e:
316 326
                 log = 'Timeout error to transmit fetched mail to tracim : {}'
317 327
                 logger.error(self, log.format(str(e)))
318
-                unsended_mail.append(mail)
328
+                unsended_mails.append(mail)
319 329
                 break
320 330
             except requests.exceptions.RequestException as e:
321 331
                 log = 'Fail to transmit fetched mail to tracim : {}'
322 332
                 logger.error(self, log.format(str(e)))
323 333
                 break
334
+
324 335
         return unsended_mails