33see: https://www.mediawiki.org/wiki/API:Emailuser
44"""
55import logging
6+ from json import dumps
67from requests import Session
78from requests .exceptions import ConnectionError
89from requests .structures import CaseInsensitiveDict
@@ -40,7 +41,7 @@ def conn(*args, **kwargs):
4041 logger .warning ("ConnectionError exhausted retries" )
4142 raise e
4243 logger .warning (
43- "ConnectionError, retrying in {}s" .format (self . retry_after_conn )
44+ "ConnectionError, retrying in {}s" .format (retry_after_conn )
4445 )
4546 sleep (retry_after_conn )
4647 continue
@@ -50,43 +51,6 @@ def conn(*args, **kwargs):
5051 return wrapper
5152
5253
53- def _handle_maxlag (response ):
54- """A helper method that handles maxlag retries."""
55- data = response .json ()
56- try :
57- if data ["error" ]["code" ] != "maxlag" :
58- return data
59- except KeyError :
60- return data
61-
62- retry_after = float (response .headers .get ("Retry-After" , 5 ))
63- retry_on_lag_error = 50
64- no_retry = 0 <= retry_on_lag_error < try_count
65-
66- message = "Server exceeded maxlag"
67- if not no_retry :
68- message += ", retrying in {}s" .format (retry_after )
69- if "lag" in data ["error" ]:
70- message += ", lag={}" .format (data ["error" ]["lag" ])
71- message += ", API=" .format (self .url )
72-
73- log = logger .warning if no_retry else logger .info
74- log (
75- message ,
76- {
77- "code" : "maxlag-retry" ,
78- "retry-after" : retry_after ,
79- "lag" : data ["error" ]["lag" ] if "lag" in data ["error" ] else None ,
80- "x-database-lag" : response .headers .get ("X-Database-Lag" , 5 ),
81- },
82- )
83-
84- if no_retry :
85- raise Exception (message )
86-
87- sleep (retry_after )
88-
89-
9054class EmailBackend (BaseEmailBackend ):
9155 def __init__ (
9256 self ,
@@ -117,6 +81,56 @@ def __init__(
11781 self .session = None
11882 logger .info ("Email connection constructed." )
11983
84+ def _handle_request (self , response , try_count = 0 ):
85+ """
86+ A helper method that handles MW API responses
87+ including maxlag retries.
88+ """
89+ # Raise for any HTTP response errors
90+ if response .status_code != 200 :
91+ raise Exception ("HTTP {} error" .format (response .status_code ))
92+ data = response .json ()
93+ error = data .get ("error" , {})
94+ if "warnings" in data :
95+ logger .warning (dumps (data ["warnings" ], indent = True ))
96+ # raise for any api error codes besides max lag
97+ try :
98+ if error .get ("code" ) != "maxlag" :
99+ raise Exception (dumps (error ))
100+ except :
101+ # return data if there are no errors
102+ return data
103+
104+ # handle retries with max lag
105+ lag = error .get ("lag" )
106+ request = response .request
107+ retry_after = float (response .headers .get ("Retry-After" , 5 ))
108+ retry_on_lag_error = 50
109+ no_retry = 0 <= retry_on_lag_error < try_count
110+ message = "Server exceeded maxlag"
111+ if not no_retry :
112+ message += ", retrying in {}s" .format (retry_after )
113+ if lag :
114+ message += ", lag={}" .format (lag )
115+ message += ", url={}" .format (self .url )
116+ log = logger .warning if no_retry else logger .info
117+ log (
118+ message ,
119+ {
120+ "code" : "maxlag-retry" ,
121+ "retry-after" : retry_after ,
122+ "lag" : lag ,
123+ "x-database-lag" : response .headers .get ("X-Database-Lag" , 5 ),
124+ },
125+ )
126+ if no_retry :
127+ raise Exception (message )
128+
129+ sleep (retry_after )
130+ try_count += 1
131+ return self ._handle_request (self .session .send (request ), try_count )
132+
133+ @retry_conn ()
120134 def open (self ):
121135 """
122136 Ensure an open session to the API server. Return whether or not a
@@ -128,6 +142,10 @@ def open(self):
128142 return False
129143
130144 try :
145+ self .session = Session ()
146+ self .session .headers = self .headers
147+ logger .info ("Session created, getting login token..." )
148+
131149 # GET request to fetch login token
132150 login_token_params = {
133151 "action" : "query" ,
@@ -136,17 +154,12 @@ def open(self):
136154 "maxlag" : self .maxlag ,
137155 "format" : "json" ,
138156 }
139- session = Session ()
140- session .headers = self .headers
141- logger .info ("Session created, getting login token..." )
142- response_login_token = session .get (url = self .url , params = login_token_params )
143- if response_login_token .status_code != 200 :
144- raise Exception (
145- "There was an error in the request for obtaining the login token."
146- )
147- login_token_data = _handle_maxlag (response_login_token )
148- login_token = login_token_data ["query" ]["tokens" ]["logintoken" ]
157+ login_token_response = self ._handle_request (
158+ self .session .get (url = self .url , params = login_token_params )
159+ )
160+ login_token = login_token_response ["query" ]["tokens" ]["logintoken" ]
149161 if not login_token :
162+ self .session = None
150163 raise Exception ("There was an error obtaining the login token." )
151164
152165 # POST request to log in. Use of main account for login is not
@@ -161,30 +174,25 @@ def open(self):
161174 "format" : "json" ,
162175 }
163176 logger .info ("Signing in..." )
164- login_response = session . post ( url = self .url , data = login_params )
165- if login_response . status_code != 200 :
166- raise Exception ( "There was an error in the request for the login." )
177+ login_response = self ._handle_request (
178+ self . session . post ( url = self . url , data = login_params )
179+ )
167180
168181 # GET request to fetch Email token
169182 # see: https://www.mediawiki.org/wiki/API:Emailuser#Token
170183 email_token_params = {"action" : "query" , "meta" : "tokens" , "format" : "json" }
171184
172185 logger .info ("Getting email token..." )
173- email_token_response = session .get (url = self .url , params = email_token_params )
174- if email_token_response .status_code != 200 :
175- raise Exception (
176- "There was an error in the request for the email token."
177- )
178-
179- email_token_data = _handle_maxlag (email_token_response )
180-
181- email_token = email_token_data ["query" ]["tokens" ]["csrftoken" ]
186+ email_token_response = self ._handle_request (
187+ self .session .get (url = self .url , params = email_token_params )
188+ )
189+ email_token = email_token_response ["query" ]["tokens" ]["csrftoken" ]
182190 if not email_token :
191+ self .session = None
183192 raise Exception ("There was an error obtaining the email token." )
184193
185- # Assign the session and email token
194+ # Assign the email token
186195 self .email_token = email_token
187- self .session = session
188196 logger .info ("Email API session ready." )
189197 return True
190198 except :
@@ -251,15 +259,10 @@ def _send(self, email_message):
251259 }
252260
253261 logger .info ("Checking if user is emailable..." )
254- emailable_response = self .session . post (
255- url = self .url , data = emailable_params
262+ emailable_response = self ._handle_request (
263+ self . session . post ( url = self .url , data = emailable_params )
256264 )
257- if emailable_response .status_code != 200 :
258- raise Exception (
259- "There was an error in the request to check if the user can receive emails."
260- )
261- emailable_data = _handle_maxlag (emailable_response )
262- emailable = "emailable" in emailable_data ["query" ]["users" ][0 ]
265+ emailable = "emailable" in emailable_response ["query" ]["users" ][0 ]
263266 if not emailable :
264267 raise Exception ("User not emailable, email skipped." )
265268
@@ -275,13 +278,10 @@ def _send(self, email_message):
275278 }
276279
277280 logger .info ("Sending email..." )
278- emailuser_response = self .session .post (url = self .url , data = email_params )
279- if emailuser_response .status_code != 200 :
280- raise Exception (
281- "There was an error in the request to send the email."
282- )
283- emailuser_data = _handle_maxlag (emailuser_response )
284- if emailuser_data ["emailuser" ]["result" ] != "Success" :
281+ emailuser_response = self ._handle_request (
282+ self .session .post (url = self .url , data = email_params )
283+ )
284+ if emailuser_response ["emailuser" ]["result" ] != "Success" :
285285 raise Exception ("There was an error when trying to send the email." )
286286 logger .info ("Email sent." )
287287 except :
0 commit comments