@@ -280,8 +280,7 @@ class Page(CopyvioMixIn): | |||
self._assert_existence() | |||
def _edit(self, params=None, text=None, summary=None, minor=None, bot=None, | |||
force=None, section=None, captcha_id=None, captcha_word=None, | |||
tries=0): | |||
force=None, section=None, captcha_id=None, captcha_word=None): | |||
"""Edit the page! | |||
If *params* is given, we'll use it as our API query parameters. | |||
@@ -316,7 +315,7 @@ class Page(CopyvioMixIn): | |||
except exceptions.APIError as error: | |||
if not hasattr(error, "code"): | |||
raise # We can only handle errors with a code attribute | |||
result = self._handle_edit_errors(error, params, tries) | |||
result = self._handle_edit_errors(error, params) | |||
# If everything was successful, reset invalidated attributes: | |||
if result["edit"]["result"] == "Success": | |||
@@ -360,12 +359,12 @@ class Page(CopyvioMixIn): | |||
return params | |||
def _handle_edit_errors(self, error, params, tries): | |||
def _handle_edit_errors(self, error, params, retry=True): | |||
"""If our edit fails due to some error, try to handle it. | |||
We'll either raise an appropriate exception (for example, if the page | |||
is protected), or we'll try to fix it (for example, if we can't edit | |||
due to being logged out, we'll try to log in). | |||
is protected), or we'll try to fix it (for example, if the token is | |||
invalid, we'll try to get a new one). | |||
""" | |||
perms = ["noedit", "noedit-anon", "cantcreate", "cantcreate-anon", | |||
"protectedtitle", "noimageredirect", "noimageredirect-anon", | |||
@@ -378,6 +377,14 @@ class Page(CopyvioMixIn): | |||
self._basetimestamp = None | |||
self._exists = self.PAGE_UNKNOWN | |||
raise exceptions.EditConflictError(error.info) | |||
elif error.code == "badtoken" and retry: | |||
params["token"] = self.site.get_token("edit") | |||
try: | |||
return self.site.api_query(**params) | |||
except exceptions.APIError as error: | |||
if not hasattr(error, "code"): | |||
raise # We can only handle errors with a code attribute | |||
result = self._handle_edit_errors(error, params, retry=False) | |||
elif error.code in ["emptypage", "emptynewsection"]: | |||
raise exceptions.NoContentError(error.info) | |||
elif error.code == "contenttoobig": | |||
@@ -26,7 +26,7 @@ from json import loads | |||
from logging import getLogger, NullHandler | |||
from os.path import expanduser | |||
from StringIO import StringIO | |||
from threading import Lock | |||
from threading import RLock | |||
from time import sleep, time | |||
from urllib import quote_plus, unquote_plus | |||
from urllib2 import build_opener, HTTPCookieProcessor, URLError | |||
@@ -124,7 +124,7 @@ class Site(object): | |||
self._wait_between_queries = wait_between_queries | |||
self._max_retries = 6 | |||
self._last_query_time = 0 | |||
self._api_lock = Lock() | |||
self._api_lock = RLock() | |||
self._api_info_cache = {"maxlag": 0, "lastcheck": 0} | |||
# Attributes used for SQL queries: | |||
@@ -133,7 +133,7 @@ class Site(object): | |||
else: | |||
self._sql_data = {} | |||
self._sql_conn = None | |||
self._sql_lock = Lock() | |||
self._sql_lock = RLock() | |||
self._sql_info_cache = {"replag": 0, "lastcheck": 0, "usable": None} | |||
# Attribute used in copyright violation checks (see CopyrightMixIn): | |||
@@ -298,7 +298,7 @@ class Site(object): | |||
# Try to log in if we got logged out: | |||
self._login(self._login_info) | |||
if "token" in params: # Fetch a new one; this is invalid now | |||
params["token"] = self.get_token(params["action"], False) | |||
params["token"] = self.get_token(params["action"]) | |||
return self._api_query(params, tries, wait, ae_retry=False) | |||
if not all(self._login_info): | |||
e = "Assertion failed, and no login info was provided." | |||
@@ -762,27 +762,20 @@ class Site(object): | |||
result = list(self.sql_query(query)) | |||
return int(result[0][0]) | |||
def get_token(self, action, lock=True): | |||
def get_token(self, action): | |||
"""Return a token for a data-modifying API action. | |||
*action* must be one of the types listed on | |||
<https://www.mediawiki.org/wiki/API:Tokens>. If it's given as a union | |||
of types separated by |, then the function will return a dictionary | |||
of tokens instead of a single one. *lock* is for internal usage; | |||
setting it to ``False`` prevents the query from acquiring the internal | |||
API access lock. | |||
of tokens instead of a single one. | |||
Raises :py:exc:`~earwigbot.exceptions.PermissionsError` if we don't | |||
have permissions for the requested action(s), or they are invalid. | |||
Raises :py:exc:`~earwigbot.exceptions.APIError` if there was some other | |||
API issue. | |||
""" | |||
if lock: | |||
res = self.api_query(action="tokens", type=action) | |||
else: | |||
params = {"action": "tokens", "type": action} | |||
res = self._api_query(params, ae_retry=False) | |||
res = self.api_query(action="tokens", type=action) | |||
if "warnings" in res and "tokens" in res["warnings"]: | |||
raise exceptions.PermissionsError(res["warnings"]["tokens"]["*"]) | |||
if "|" in action: | |||