Skip to content

Commit c2cd3e6

Browse files
committed
emails.backends.mediawiki: improve error handling
- correct connection and maxlag retry bugs - bubble up api error messages - more DRY Bug: T412427
1 parent 44b83b5 commit c2cd3e6

File tree

1 file changed

+77
-77
lines changed

1 file changed

+77
-77
lines changed

TWLight/emails/backends/mediawiki.py

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
see: https://www.mediawiki.org/wiki/API:Emailuser
44
"""
55
import logging
6+
from json import dumps
67
from requests import Session
78
from requests.exceptions import ConnectionError
89
from 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-
9054
class 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

Comments
 (0)