| @@ -49,4 +49,12 @@ if not __release__: | |||
| finally: | |||
| del _add_git_commit_id_to_version_string | |||
| from earwigbot import bot, commands, config, irc, managers, tasks, util, wiki | |||
| from earwigbot import bot | |||
| from earwigbot import commands | |||
| from earwigbot import config | |||
| from earwigbot import exceptions | |||
| from earwigbot import irc | |||
| from earwigbot import managers | |||
| from earwigbot import tasks | |||
| from earwigbot import util | |||
| from earwigbot import wiki | |||
| @@ -21,34 +21,38 @@ | |||
| # SOFTWARE. | |||
| """ | |||
| EarwigBot's Wiki Toolset: Exceptions | |||
| This module contains all exceptions used by the wiki package. There are a lot: | |||
| -- SiteNotFoundError | |||
| -- SiteAPIError | |||
| -- LoginError | |||
| -- NamespaceNotFoundError | |||
| -- PageNotFoundError | |||
| -- InvalidPageError | |||
| -- RedirectError | |||
| -- UserNotFoundError | |||
| -- EditError | |||
| -- PermissionsError | |||
| -- EditConflictError | |||
| -- NoContentError | |||
| -- ContentTooBigError | |||
| -- SpamDetectedError | |||
| -- FilteredError | |||
| -- SQLError | |||
| -- CopyvioCheckError | |||
| -- UnknownSearchEngineError | |||
| -- UnsupportedSearchEngineError | |||
| -- SearchQueryError | |||
| EarwigBot Exceptions | |||
| This module contains all exceptions used by EarwigBot:: | |||
| EarwigBotError | |||
| +-- WikiToolsetError | |||
| +-- SiteNotFoundError | |||
| +-- SiteAPIError | |||
| +-- LoginError | |||
| +-- NamespaceNotFoundError | |||
| +-- PageNotFoundError | |||
| +-- InvalidPageError | |||
| +-- RedirectError | |||
| +-- UserNotFoundError | |||
| +-- EditError | |||
| | +-- PermissionsError | |||
| | +-- EditConflictError | |||
| | +-- NoContentError | |||
| | +-- ContentTooBigError | |||
| | +-- SpamDetectedError | |||
| | +-- FilteredError | |||
| +-- SQLError | |||
| +-- CopyvioCheckError | |||
| +-- UnknownSearchEngineError | |||
| +-- UnsupportedSearchEngineError | |||
| +-- SearchQueryError | |||
| """ | |||
| class WikiToolsetError(Exception): | |||
| class EarwigBotErorr(Exception): | |||
| """Base exception class for errors in EarwigBot.""" | |||
| class WikiToolsetError(EarwigBotErorr): | |||
| """Base exception class for errors in the Wiki Toolset.""" | |||
| class SiteNotFoundError(WikiToolsetError): | |||
| @@ -37,7 +37,6 @@ Category, and User) needs. | |||
| from earwigbot.wiki.category import * | |||
| from earwigbot.wiki.constants import * | |||
| from earwigbot.wiki.exceptions import * | |||
| from earwigbot.wiki.page import * | |||
| from earwigbot.wiki.site import * | |||
| from earwigbot.wiki.sitesdb import * | |||
| @@ -35,7 +35,8 @@ earwigbot.wiki (e.g. `earwigbot.wiki.USER_AGENT`). | |||
| # Default User Agent when making API queries: | |||
| from earwigbot import __version__ as _v | |||
| from platform import python_version as _p | |||
| USER_AGENT = "EarwigBot/{0} (Python/{1}; https://github.com/earwig/earwigbot)".format(_v, _p()) | |||
| USER_AGENT = "EarwigBot/{0} (Python/{1}; https://github.com/earwig/earwigbot)" | |||
| USER_AGENT = USER_AGENT.format(_v, _p()) | |||
| del _v, _p | |||
| # Default namespace IDs: | |||
| @@ -35,7 +35,7 @@ try: | |||
| except ImportError: | |||
| oauth = None | |||
| from earwigbot.wiki.exceptions import * | |||
| from earwigbot.exceptions import * | |||
| class _CopyvioCheckResult(object): | |||
| def __init__(self, violation, confidence, url, queries, article, chains): | |||
| @@ -25,8 +25,8 @@ import re | |||
| from time import gmtime, strftime | |||
| from urllib import quote | |||
| from earwigbot import exceptions | |||
| from earwigbot.wiki.copyright import CopyrightMixin | |||
| from earwigbot.wiki.exceptions import * | |||
| __all__ = ["Page"] | |||
| @@ -132,7 +132,7 @@ class Page(CopyrightMixin): | |||
| """ | |||
| if self._exists == 1: | |||
| e = "Page '{0}' is invalid.".format(self._title) | |||
| raise InvalidPageError(e) | |||
| raise exceptions.InvalidPageError(e) | |||
| def _force_existence(self): | |||
| """Used to ensure that our page exists. | |||
| @@ -144,7 +144,7 @@ class Page(CopyrightMixin): | |||
| self._force_validity() | |||
| if self._exists == 2: | |||
| e = "Page '{0}' does not exist.".format(self._title) | |||
| raise PageNotFoundError(e) | |||
| raise exceptions.PageNotFoundError(e) | |||
| def _load_wrapper(self): | |||
| """Calls _load_attributes() and follows redirects if we're supposed to. | |||
| @@ -278,8 +278,8 @@ class Page(CopyrightMixin): | |||
| self._load_attributes() | |||
| if not self._token: | |||
| e = "You don't have permission to edit this page." | |||
| raise PermissionsError(e) | |||
| raise exceptions.PermissionsError(e) | |||
| # Weed out invalid pages before we get too far: | |||
| self._force_validity() | |||
| @@ -310,7 +310,7 @@ class Page(CopyrightMixin): | |||
| try: | |||
| assertion = result["edit"]["assert"] | |||
| except KeyError: | |||
| raise EditError(result["edit"]) | |||
| raise exceptions.EditError(result["edit"]) | |||
| self._handle_assert_edit(assertion, params, tries) | |||
| def _build_edit_params(self, text, summary, minor, bot, force, section, | |||
| @@ -353,13 +353,13 @@ class Page(CopyrightMixin): | |||
| """ | |||
| if error.code in ["noedit", "cantcreate", "protectedtitle", | |||
| "noimageredirect"]: | |||
| raise PermissionsError(error.info) | |||
| raise exceptions.PermissionsError(error.info) | |||
| elif error.code in ["noedit-anon", "cantcreate-anon", | |||
| "noimageredirect-anon"]: | |||
| if not all(self._site._login_info): | |||
| # Insufficient login info: | |||
| raise PermissionsError(error.info) | |||
| raise exceptions.PermissionsError(error.info) | |||
| if tries == 0: | |||
| # We have login info; try to login: | |||
| self._site._login(self._site._login_info) | |||
| @@ -368,28 +368,28 @@ class Page(CopyrightMixin): | |||
| else: | |||
| # We already tried to log in and failed! | |||
| e = "Although we should be logged in, we are not. This may be a cookie problem or an odd bug." | |||
| raise LoginError(e) | |||
| raise exceptions.LoginError(e) | |||
| elif error.code in ["editconflict", "pagedeleted", "articleexists"]: | |||
| # These attributes are now invalidated: | |||
| self._content = None | |||
| self._basetimestamp = None | |||
| self._exists = 0 | |||
| raise EditConflictError(error.info) | |||
| raise exceptions.EditConflictError(error.info) | |||
| elif error.code in ["emptypage", "emptynewsection"]: | |||
| raise NoContentError(error.info) | |||
| raise exceptions.NoContentError(error.info) | |||
| elif error.code == "contenttoobig": | |||
| raise ContentTooBigError(error.info) | |||
| raise exceptions.ContentTooBigError(error.info) | |||
| elif error.code == "spamdetected": | |||
| raise SpamDetectedError(error.info) | |||
| raise exceptions.SpamDetectedError(error.info) | |||
| elif error.code == "filtered": | |||
| raise FilteredError(error.info) | |||
| raise exceptions.FilteredError(error.info) | |||
| raise EditError(": ".join((error.code, error.info))) | |||
| raise exceptions.EditError(": ".join((error.code, error.info))) | |||
| def _handle_assert_edit(self, assertion, params, tries): | |||
| """If we can't edit due to a failed AssertEdit assertion, handle that. | |||
| @@ -401,7 +401,7 @@ class Page(CopyrightMixin): | |||
| if not all(self._site._login_info): | |||
| # Insufficient login info: | |||
| e = "AssertEdit: user assertion failed, and no login info was provided." | |||
| raise PermissionsError(e) | |||
| raise exceptions.PermissionsError(e) | |||
| if tries == 0: | |||
| # We have login info; try to login: | |||
| self._site._login(self._site._login_info) | |||
| @@ -410,15 +410,15 @@ class Page(CopyrightMixin): | |||
| else: | |||
| # We already tried to log in and failed! | |||
| e = "Although we should be logged in, we are not. This may be a cookie problem or an odd bug." | |||
| raise LoginError(e) | |||
| raise exceptions.LoginError(e) | |||
| elif assertion == "bot": | |||
| e = "AssertEdit: bot assertion failed; we don't have a bot flag!" | |||
| raise PermissionsError(e) | |||
| raise exceptions.PermissionsError(e) | |||
| # Unknown assertion, maybe "true", "false", or "exists": | |||
| e = "AssertEdit: assertion '{0}' failed.".format(assertion) | |||
| raise PermissionsError(e) | |||
| raise exceptions.PermissionsError(e) | |||
| def title(self, force=False): | |||
| """Returns the Page's title, or pagename. | |||
| @@ -570,7 +570,7 @@ class Page(CopyrightMixin): | |||
| if self._namespace < 0: | |||
| ns = self._site.namespace_id_to_name(self._namespace) | |||
| e = "Pages in the {0} namespace can't have talk pages.".format(ns) | |||
| raise InvalidPageError(e) | |||
| raise exceptions.InvalidPageError(e) | |||
| if self._is_talkpage: | |||
| new_ns = self._namespace - 1 | |||
| @@ -650,7 +650,7 @@ class Page(CopyrightMixin): | |||
| return re.findall(self.re_redirect, content, flags=re.I)[0] | |||
| except IndexError: | |||
| e = "The page does not appear to have a redirect target." | |||
| raise RedirectError(e) | |||
| raise exceptions.RedirectError(e) | |||
| def edit(self, text, summary, minor=False, bot=True, force=False): | |||
| """Replaces the page's content or creates a new page. | |||
| @@ -38,9 +38,9 @@ try: | |||
| except ImportError: | |||
| oursql = None | |||
| from earwigbot import exceptions | |||
| from earwigbot.wiki import constants | |||
| from earwigbot.wiki.category import Category | |||
| from earwigbot.wiki.constants import * | |||
| from earwigbot.wiki.exceptions import * | |||
| from earwigbot.wiki.page import Page | |||
| from earwigbot.wiki.user import User | |||
| @@ -128,7 +128,7 @@ class Site(object): | |||
| else: | |||
| self._cookiejar = CookieJar() | |||
| if not user_agent: | |||
| user_agent = USER_AGENT # Set default UA from wiki.constants | |||
| user_agent = constants.USER_AGENT # Set default UA | |||
| self._opener = build_opener(HTTPCookieProcessor(self._cookiejar)) | |||
| self._opener.addheaders = [("User-Agent", user_agent), | |||
| ("Accept-Encoding", "gzip")] | |||
| @@ -232,7 +232,7 @@ class Site(object): | |||
| e = e.format(error.code) | |||
| else: | |||
| e = "API query failed." | |||
| raise SiteAPIError(e) | |||
| raise exceptions.SiteAPIError(e) | |||
| result = response.read() | |||
| if response.headers.get("Content-Encoding") == "gzip": | |||
| @@ -246,7 +246,7 @@ class Site(object): | |||
| """Given API query params, return the URL to query and POST data.""" | |||
| if not self._base_url or self._script_path is None: | |||
| e = "Tried to do an API query, but no API URL is known." | |||
| raise SiteAPIError(e) | |||
| raise exceptions.SiteAPIError(e) | |||
| base_url = self._base_url | |||
| if base_url.startswith("//"): # Protocol-relative URLs from 1.18 | |||
| @@ -271,7 +271,7 @@ class Site(object): | |||
| res = loads(result) # Try to parse as a JSON object | |||
| except ValueError: | |||
| e = "API query failed: JSON could not be decoded." | |||
| raise SiteAPIError(e) | |||
| raise exceptions.SiteAPIError(e) | |||
| try: | |||
| code = res["error"]["code"] | |||
| @@ -282,7 +282,7 @@ class Site(object): | |||
| if code == "maxlag": # We've been throttled by the server | |||
| if tries >= self._max_retries: | |||
| e = "Maximum number of retries reached ({0})." | |||
| raise SiteAPIError(e.format(self._max_retries)) | |||
| raise exceptions.SiteAPIError(e.format(self._max_retries)) | |||
| tries += 1 | |||
| msg = 'Server says "{0}"; retrying in {1} seconds ({2}/{3})' | |||
| self._logger.info(msg.format(info, wait, tries, self._max_retries)) | |||
| @@ -290,7 +290,7 @@ class Site(object): | |||
| return self._api_query(params, tries=tries, wait=wait*3) | |||
| else: # Some unknown error occurred | |||
| e = 'API query failed: got error "{0}"; server says: "{1}".' | |||
| error = SiteAPIError(e.format(code, info)) | |||
| error = earwigbot.SiteAPIError(e.format(code, info)) | |||
| error.code, error.info = code, info | |||
| raise error | |||
| @@ -491,7 +491,7 @@ class Site(object): | |||
| e = "The given password is incorrect." | |||
| else: | |||
| e = "Couldn't login; server says '{0}'.".format(res) | |||
| raise LoginError(e) | |||
| raise exceptions.LoginError(e) | |||
| def _logout(self): | |||
| """Safely logout through the API. | |||
| @@ -518,7 +518,7 @@ class Site(object): | |||
| """ | |||
| if not oursql: | |||
| e = "Module 'oursql' is required for SQL queries." | |||
| raise SQLError(e) | |||
| raise exceptions.SQLError(e) | |||
| args = self._sql_data | |||
| for key, value in kwargs.iteritems(): | |||
| @@ -638,7 +638,7 @@ class Site(object): | |||
| return self._namespaces[ns_id][0] | |||
| except KeyError: | |||
| e = "There is no namespace with id {0}.".format(ns_id) | |||
| raise NamespaceNotFoundError(e) | |||
| raise exceptions.NamespaceNotFoundError(e) | |||
| def namespace_name_to_id(self, name): | |||
| """Given a namespace name, returns the associated ID. | |||
| @@ -655,7 +655,7 @@ class Site(object): | |||
| return ns_id | |||
| e = "There is no namespace with name '{0}'.".format(name) | |||
| raise NamespaceNotFoundError(e) | |||
| raise exceptions.NamespaceNotFoundError(e) | |||
| def get_page(self, title, follow_redirects=False): | |||
| """Returns a Page object for the given title (pagename). | |||
| @@ -667,7 +667,7 @@ class Site(object): | |||
| Note that this doesn't do any direct checks for existence or | |||
| redirect-following - Page's methods provide that. | |||
| """ | |||
| prefixes = self.namespace_id_to_name(NS_CATEGORY, all=True) | |||
| prefixes = self.namespace_id_to_name(constants.NS_CATEGORY, all=True) | |||
| prefix = title.split(":", 1)[0] | |||
| if prefix != title: # Avoid a page that is simply "Category" | |||
| if prefix in prefixes: | |||
| @@ -680,7 +680,7 @@ class Site(object): | |||
| `catname` should be given *without* a namespace prefix. This method is | |||
| really just shorthand for get_page("Category:" + catname). | |||
| """ | |||
| prefix = self.namespace_id_to_name(NS_CATEGORY) | |||
| prefix = self.namespace_id_to_name(constants.NS_CATEGORY) | |||
| pagename = ':'.join((prefix, catname)) | |||
| return Category(self, pagename, follow_redirects) | |||
| @@ -28,7 +28,7 @@ import stat | |||
| import sqlite3 as sqlite | |||
| from earwigbot import __version__ | |||
| from earwigbot.wiki.exceptions import SiteNotFoundError | |||
| from earwigbot.exceptions import SiteNotFoundError | |||
| from earwigbot.wiki.site import Site | |||
| __all__ = ["SitesDB"] | |||
| @@ -22,8 +22,8 @@ | |||
| from time import gmtime, strptime | |||
| from earwigbot.wiki.constants import * | |||
| from earwigbot.wiki.exceptions import UserNotFoundError | |||
| from earwigbot.exceptions import UserNotFoundError | |||
| from earwigbot.wiki import constants | |||
| from earwigbot.wiki.page import Page | |||
| __all__ = ["User"] | |||
| @@ -252,7 +252,7 @@ class User(object): | |||
| No checks are made to see if it exists or not. Proper site namespace | |||
| conventions are followed. | |||
| """ | |||
| prefix = self._site.namespace_id_to_name(NS_USER) | |||
| prefix = self._site.namespace_id_to_name(constants.NS_USER) | |||
| pagename = ':'.join((prefix, self._name)) | |||
| return Page(self._site, pagename) | |||
| @@ -262,6 +262,6 @@ class User(object): | |||
| No checks are made to see if it exists or not. Proper site namespace | |||
| conventions are followed. | |||
| """ | |||
| prefix = self._site.namespace_id_to_name(NS_USER_TALK) | |||
| prefix = self._site.namespace_id_to_name(constants.NS_USER_TALK) | |||
| pagename = ':'.join((prefix, self._name)) | |||
| return Page(self._site, pagename) | |||