| @@ -78,7 +78,7 @@ class Command(BaseCommand): | |||||
| short = self.statistics.get_short_title(page.title) | short = self.statistics.get_short_title(page.title) | ||||
| status = self.get_status(page) | status = self.get_status(page) | ||||
| user = self.site.get_user(page.creator()) | user = self.site.get_user(page.creator()) | ||||
| user_name = user.name() | |||||
| user_name = user.name | |||||
| user_url = user.get_talkpage().url | user_url = user.get_talkpage().url | ||||
| msg1 = "AfC submission report for \x0302{0}\x0301 ({1}):" | msg1 = "AfC submission report for \x0302{0}\x0301 ({1}):" | ||||
| @@ -45,13 +45,13 @@ class Command(BaseCommand): | |||||
| user = site.get_user(name) | user = site.get_user(name) | ||||
| try: | try: | ||||
| count = user.editcount() | |||||
| count = user.editcount | |||||
| except wiki.UserNotFoundError: | except wiki.UserNotFoundError: | ||||
| msg = "the user \x0302{0}\x0301 does not exist." | msg = "the user \x0302{0}\x0301 does not exist." | ||||
| self.reply(data, msg.format(name)) | self.reply(data, msg.format(name)) | ||||
| return | return | ||||
| safe = quote_plus(user.name()) | |||||
| safe = quote_plus(user.name) | |||||
| url = "http://toolserver.org/~tparis/pcount/index.php?name={0}&lang=en&wiki=wikipedia" | url = "http://toolserver.org/~tparis/pcount/index.php?name={0}&lang=en&wiki=wikipedia" | ||||
| msg = "\x0302{0}\x0301 has {1} edits ({2})." | msg = "\x0302{0}\x0301 has {1} edits ({2})." | ||||
| self.reply(data, msg.format(name, count, url.format(safe))) | self.reply(data, msg.format(name, count, url.format(safe))) | ||||
| @@ -45,7 +45,7 @@ class Command(BaseCommand): | |||||
| user = site.get_user(name) | user = site.get_user(name) | ||||
| try: | try: | ||||
| reg = user.registration() | |||||
| reg = user.registration | |||||
| except wiki.UserNotFoundError: | except wiki.UserNotFoundError: | ||||
| msg = "the user \x0302{0}\x0301 does not exist." | msg = "the user \x0302{0}\x0301 does not exist." | ||||
| self.reply(data, msg.format(name)) | self.reply(data, msg.format(name)) | ||||
| @@ -54,14 +54,13 @@ class Command(BaseCommand): | |||||
| date = time.strftime("%b %d, %Y at %H:%M:%S UTC", reg) | date = time.strftime("%b %d, %Y at %H:%M:%S UTC", reg) | ||||
| age = self.get_diff(time.mktime(reg), time.mktime(time.gmtime())) | age = self.get_diff(time.mktime(reg), time.mktime(time.gmtime())) | ||||
| g = user.gender() | |||||
| if g == "male": | |||||
| if user.gender == "male": | |||||
| gender = "He's" | gender = "He's" | ||||
| elif g == "female": | |||||
| elif user.gender == "female": | |||||
| gender = "She's" | gender = "She's" | ||||
| else: | else: | ||||
| gender = "They're" | gender = "They're" | ||||
| msg = "\x0302{0}\x0301 registered on {1}. {2} {3} old." | msg = "\x0302{0}\x0301 registered on {1}. {2} {3} old." | ||||
| self.reply(data, msg.format(name, date, gender, age)) | self.reply(data, msg.format(name, date, gender, age)) | ||||
| @@ -43,7 +43,7 @@ class Command(BaseCommand): | |||||
| user = site.get_user(name) | user = site.get_user(name) | ||||
| try: | try: | ||||
| rights = user.groups() | |||||
| rights = user.groups | |||||
| except wiki.UserNotFoundError: | except wiki.UserNotFoundError: | ||||
| msg = "the user \x0302{0}\x0301 does not exist." | msg = "the user \x0302{0}\x0301 does not exist." | ||||
| self.reply(data, msg.format(name)) | self.reply(data, msg.format(name)) | ||||
| @@ -116,7 +116,7 @@ class BaseTask(object): | |||||
| except KeyError: | except KeyError: | ||||
| return False | return False | ||||
| title = cfg.get("page", "User:$1/Shutoff/Task $2") | title = cfg.get("page", "User:$1/Shutoff/Task $2") | ||||
| username = site.get_user().name() | |||||
| username = site.get_user().name | |||||
| title = title.replace("$1", username).replace("$2", str(self.number)) | title = title.replace("$1", username).replace("$2", str(self.number)) | ||||
| page = site.get_page(title) | page = site.get_page(title) | ||||
| @@ -718,7 +718,7 @@ class Task(BaseTask): | |||||
| if chart in [self.CHART_PEND, self.CHART_DRAFT] and s_user: | if chart in [self.CHART_PEND, self.CHART_DRAFT] and s_user: | ||||
| submitter = self.site.get_user(s_user) | submitter = self.site.get_user(s_user) | ||||
| try: | try: | ||||
| if submitter.blockinfo(): | |||||
| if submitter.blockinfo: | |||||
| notes += "|nb=1" # Submitter is blocked | notes += "|nb=1" # Submitter is blocked | ||||
| except wiki.UserNotFoundError: # Likely an IP | except wiki.UserNotFoundError: # Likely an IP | ||||
| pass | pass | ||||
| @@ -43,12 +43,12 @@ class Category(Page): | |||||
| """ | """ | ||||
| def __repr__(self): | def __repr__(self): | ||||
| """Returns the canonical string representation of the Category.""" | |||||
| """Return the canonical string representation of the Category.""" | |||||
| res = "Category(title={0!r}, follow_redirects={1!r}, site={2!r})" | res = "Category(title={0!r}, follow_redirects={1!r}, site={2!r})" | ||||
| return res.format(self._title, self._follow_redirects, self._site) | return res.format(self._title, self._follow_redirects, self._site) | ||||
| def __str__(self): | def __str__(self): | ||||
| """Returns a nice string representation of the Category.""" | |||||
| """Return a nice string representation of the Category.""" | |||||
| return '<Category "{0}" of {1}>'.format(self.title, str(self._site)) | return '<Category "{0}" of {1}>'.format(self.title, str(self._site)) | ||||
| def _get_members_via_sql(self, limit): | def _get_members_via_sql(self, limit): | ||||
| @@ -87,7 +87,7 @@ class Category(Page): | |||||
| return [member["title"] for member in members] | return [member["title"] for member in members] | ||||
| def get_members(self, use_sql=False, limit=None): | def get_members(self, use_sql=False, limit=None): | ||||
| """Returns a list of page titles in the category. | |||||
| """Return a list of page titles in the category. | |||||
| If *use_sql* is ``True``, we will use a SQL query instead of the API. | If *use_sql* is ``True``, we will use a SQL query instead of the API. | ||||
| Pages will be returned as tuples of ``(title, pageid)`` instead of just | Pages will be returned as tuples of ``(title, pageid)`` instead of just | ||||
| @@ -123,12 +123,12 @@ class Page(CopyrightMixin): | |||||
| self._is_talkpage = self._namespace % 2 == 1 | self._is_talkpage = self._namespace % 2 == 1 | ||||
| def __repr__(self): | def __repr__(self): | ||||
| """Returns the canonical string representation of the Page.""" | |||||
| """Return the canonical string representation of the Page.""" | |||||
| res = "Page(title={0!r}, follow_redirects={1!r}, site={2!r})" | res = "Page(title={0!r}, follow_redirects={1!r}, site={2!r})" | ||||
| return res.format(self._title, self._follow_redirects, self._site) | return res.format(self._title, self._follow_redirects, self._site) | ||||
| def __str__(self): | def __str__(self): | ||||
| """Returns a nice string representation of the Page.""" | |||||
| """Return a nice string representation of the Page.""" | |||||
| return '<Page "{0}" of {1}>'.format(self.title, str(self._site)) | return '<Page "{0}" of {1}>'.format(self.title, str(self._site)) | ||||
| def _assert_validity(self): | def _assert_validity(self): | ||||
| @@ -157,7 +157,7 @@ class Page(CopyrightMixin): | |||||
| raise exceptions.PageNotFoundError(e) | raise exceptions.PageNotFoundError(e) | ||||
| def _load(self): | def _load(self): | ||||
| """Calls _load_attributes() and follows redirects if we're supposed to. | |||||
| """Call _load_attributes() and follows redirects if we're supposed to. | |||||
| This method will only follow redirects if follow_redirects=True was | This method will only follow redirects if follow_redirects=True was | ||||
| passed to __init__() (perhaps indirectly passed by site.get_page()). | passed to __init__() (perhaps indirectly passed by site.get_page()). | ||||
| @@ -180,7 +180,7 @@ class Page(CopyrightMixin): | |||||
| self._load_attributes() | self._load_attributes() | ||||
| def _load_attributes(self, result=None): | def _load_attributes(self, result=None): | ||||
| """Loads various data from the API in a single query. | |||||
| """Load various data from the API in a single query. | |||||
| Loads self._title, ._exists, ._is_redirect, ._pageid, ._fullurl, | Loads self._title, ._exists, ._is_redirect, ._pageid, ._fullurl, | ||||
| ._protection, ._namespace, ._is_talkpage, ._creator, ._lastrevid, | ._protection, ._namespace, ._is_talkpage, ._creator, ._lastrevid, | ||||
| @@ -245,7 +245,7 @@ class Page(CopyrightMixin): | |||||
| pass | pass | ||||
| def _load_content(self, result=None): | def _load_content(self, result=None): | ||||
| """Loads current page content from the API. | |||||
| """Load current page content from the API. | |||||
| If *result* is provided, we'll pretend that is the result of an API | If *result* is provided, we'll pretend that is the result of an API | ||||
| query and try to get content from that. Otherwise, we'll do an API | query and try to get content from that. Otherwise, we'll do an API | ||||
| @@ -557,7 +557,7 @@ class Page(CopyrightMixin): | |||||
| self._load_content() | self._load_content() | ||||
| def toggle_talk(self, follow_redirects=None): | def toggle_talk(self, follow_redirects=None): | ||||
| """Returns a content page's talk page, or vice versa. | |||||
| """Return a content page's talk page, or vice versa. | |||||
| The title of the new page is determined by namespace logic, not API | The title of the new page is determined by namespace logic, not API | ||||
| queries. We won't make any API queries on our own. | queries. We won't make any API queries on our own. | ||||
| @@ -601,7 +601,7 @@ class Page(CopyrightMixin): | |||||
| return Page(self._site, new_title, follow_redirects) | return Page(self._site, new_title, follow_redirects) | ||||
| def get(self): | def get(self): | ||||
| """Returns page content, which is cached if you try to call get again. | |||||
| """Return page content, which is cached if you try to call get again. | |||||
| Raises InvalidPageError or PageNotFoundError if the page name is | Raises InvalidPageError or PageNotFoundError if the page name is | ||||
| invalid or the page does not exist, respectively. | invalid or the page does not exist, respectively. | ||||
| @@ -675,7 +675,7 @@ class Page(CopyrightMixin): | |||||
| return self._site.get_user(self._creator) | return self._site.get_user(self._creator) | ||||
| def edit(self, text, summary, minor=False, bot=True, force=False): | def edit(self, text, summary, minor=False, bot=True, force=False): | ||||
| """Replaces the page's content or creates a new page. | |||||
| """Replace the page's content or creates a new page. | |||||
| *text* is the new page content, with *summary* as the edit summary. | *text* is the new page content, with *summary* as the edit summary. | ||||
| If *minor* is ``True``, the edit will be marked as minor. If *bot* is | If *minor* is ``True``, the edit will be marked as minor. If *bot* is | ||||
| @@ -690,7 +690,7 @@ class Page(CopyrightMixin): | |||||
| force=force) | force=force) | ||||
| def add_section(self, text, title, minor=False, bot=True, force=False): | def add_section(self, text, title, minor=False, bot=True, force=False): | ||||
| """Adds a new section to the bottom of the page. | |||||
| """Add a new section to the bottom of the page. | |||||
| The arguments for this are the same as those for :py:meth:`edit`, but | The arguments for this are the same as those for :py:meth:`edit`, but | ||||
| instead of providing a summary, you provide a section title. | instead of providing a summary, you provide a section title. | ||||
| @@ -160,7 +160,7 @@ class Site(object): | |||||
| self._login(login) | self._login(login) | ||||
| def __repr__(self): | def __repr__(self): | ||||
| """Returns the canonical string representation of the Site.""" | |||||
| """Return the canonical string representation of the Site.""" | |||||
| res = ", ".join(( | res = ", ".join(( | ||||
| "Site(name={_name!r}", "project={_project!r}", "lang={_lang!r}", | "Site(name={_name!r}", "project={_project!r}", "lang={_lang!r}", | ||||
| "base_url={_base_url!r}", "article_path={_article_path!r}", | "base_url={_base_url!r}", "article_path={_article_path!r}", | ||||
| @@ -179,7 +179,7 @@ class Site(object): | |||||
| return res.format(login, cookies, agent, **self.__dict__) | return res.format(login, cookies, agent, **self.__dict__) | ||||
| def __str__(self): | def __str__(self): | ||||
| """Returns a nice string representation of the Site.""" | |||||
| """Return a nice string representation of the Site.""" | |||||
| res = "<Site {0} ({1}:{2}) at {3}>" | res = "<Site {0} ({1}:{2}) at {3}>" | ||||
| return res.format(self.name, self.project, self.lang, self.domain) | return res.format(self.name, self.project, self.lang, self.domain) | ||||
| @@ -704,7 +704,7 @@ class Site(object): | |||||
| return Page(self, title, follow_redirects) | return Page(self, title, follow_redirects) | ||||
| def get_category(self, catname, follow_redirects=False): | def get_category(self, catname, follow_redirects=False): | ||||
| """Returns a :py:class:`Category` object for the given category name. | |||||
| """Return a :py:class:`Category` object for the given category name. | |||||
| *catname* should be given *without* a namespace prefix. This method is | *catname* should be given *without* a namespace prefix. This method is | ||||
| really just shorthand for :py:meth:`get_page("Category:" + catname) | really just shorthand for :py:meth:`get_page("Category:" + catname) | ||||
| @@ -715,7 +715,7 @@ class Site(object): | |||||
| return Category(self, pagename, follow_redirects) | return Category(self, pagename, follow_redirects) | ||||
| def get_user(self, username=None): | def get_user(self, username=None): | ||||
| """Returns a :py:class:`User` object for the given username. | |||||
| """Return a :py:class:`User` object for the given username. | |||||
| If *username* is left as ``None``, then a | If *username* is left as ``None``, then a | ||||
| :py:class:`~earwigbot.wiki.user.User` object representing the currently | :py:class:`~earwigbot.wiki.user.User` object representing the currently | ||||
| @@ -35,20 +35,23 @@ __all__ = ["SitesDB"] | |||||
| class SitesDB(object): | class SitesDB(object): | ||||
| """ | """ | ||||
| EarwigBot's Wiki Toolset: Sites Database Manager | |||||
| **EarwigBot's Wiki Toolset: Sites Database Manager** | |||||
| This class controls the sites.db file, which stores information about all | |||||
| wiki sites known to the bot. Three public methods act as bridges between | |||||
| the bot's config files and Site objects: | |||||
| get_site -- returns a Site object corresponding to a given site name | |||||
| add_site -- stores a site in the database, given connection info | |||||
| remove_site -- removes a site from the database, given its name | |||||
| This class controls the :file:`sites.db` file, which stores information | |||||
| about all wiki sites known to the bot. Three public methods act as bridges | |||||
| between the bot's config files and :py:class:`~earwigbot.wiki.site.Site` | |||||
| objects: | |||||
| - :py:meth:`get_site`: returns a Site object corresponding to a site | |||||
| - :py:meth:`add_site`: stores a site in the database | |||||
| - :py:meth:`remove_site`: removes a site from the database | |||||
| There's usually no need to use this class directly. All public methods | There's usually no need to use this class directly. All public methods | ||||
| here are available as bot.wiki.get_site(), bot.wiki.add_site(), and | |||||
| bot.wiki.remove_site(), which use a sites.db file located in the same | |||||
| directory as our config.yml file. Lower-level access can be achieved | |||||
| by importing the manager class (`from earwigbot.wiki import SitesDB`). | |||||
| here are available as :py:meth:`bot.wiki.get_site`, | |||||
| :py:meth:`bot.wiki.add_site`, and :py:meth:`bot.wiki.remove_site`, which | |||||
| use a :file:`sites.db` file located in the same directory as our | |||||
| :file:`config.yml` file. Lower-level access can be achieved by importing | |||||
| the manager class (``from earwigbot.wiki import SitesDB``). | |||||
| """ | """ | ||||
| def __init__(self, bot): | def __init__(self, bot): | ||||
| @@ -157,7 +160,7 @@ class SitesDB(object): | |||||
| namespaces) | namespaces) | ||||
| def _make_site_object(self, name): | def _make_site_object(self, name): | ||||
| """Return a Site object associated with the site 'name' in our sitesdb. | |||||
| """Return a Site object associated with the site *name* in our sitesdb. | |||||
| This calls _load_site_from_sitesdb(), so SiteNotFoundError will be | This calls _load_site_from_sitesdb(), so SiteNotFoundError will be | ||||
| raised if the site is not in our sitesdb. | raised if the site is not in our sitesdb. | ||||
| @@ -255,24 +258,25 @@ class SitesDB(object): | |||||
| """Return a Site instance based on information from the sitesdb. | """Return a Site instance based on information from the sitesdb. | ||||
| With no arguments, return the default site as specified by our config | With no arguments, return the default site as specified by our config | ||||
| file. This is config.wiki["defaultSite"]. | |||||
| file. This is ``config.wiki["defaultSite"]``. | |||||
| With 'name' specified, return the site with that name. This is | |||||
| equivalent to the site's 'wikiid' in the API, like 'enwiki'. | |||||
| With *name* specified, return the site with that name. This is | |||||
| equivalent to the site's ``wikiid`` in the API, like *enwiki*. | |||||
| With 'project' and 'lang' specified, return the site whose project and | |||||
| With *project* and *lang* specified, return the site whose project and | |||||
| language match these values. If there are multiple sites with the same | language match these values. If there are multiple sites with the same | ||||
| values (unlikely), this is not a reliable way of loading a site. Call | values (unlikely), this is not a reliable way of loading a site. Call | ||||
| the function with an explicit 'name' in that case. | |||||
| the function with an explicit *name* in that case. | |||||
| We will attempt to login to the site automatically using | We will attempt to login to the site automatically using | ||||
| config.wiki["username"] and config.wiki["password"] if both are | |||||
| ``config.wiki["username"]`` and ``config.wiki["password"]`` if both are | |||||
| defined. | defined. | ||||
| Specifying a project without a lang or a lang without a project will | Specifying a project without a lang or a lang without a project will | ||||
| raise TypeError. If all three args are specified, 'name' will be first | |||||
| tried, then 'project' and 'lang' if 'name' doesn't work. If a site | |||||
| cannot be found in the sitesdb, SiteNotFoundError will be raised. An | |||||
| raise :py:exc:`TypeError`. If all three args are specified, *name* will | |||||
| be first tried, then *project* and *lang* if *name* doesn't work. If a | |||||
| site cannot be found in the sitesdb, | |||||
| :py:exc:`~earwigbot.exceptions.SiteNotFoundError` will be raised. An | |||||
| empty sitesdb will be created if none is found. | empty sitesdb will be created if none is found. | ||||
| """ | """ | ||||
| # Someone specified a project without a lang, or vice versa: | # Someone specified a project without a lang, or vice versa: | ||||
| @@ -311,23 +315,27 @@ class SitesDB(object): | |||||
| script_path="/w", sql=None): | script_path="/w", sql=None): | ||||
| """Add a site to the sitesdb so it can be retrieved with get_site(). | """Add a site to the sitesdb so it can be retrieved with get_site(). | ||||
| If only a project and a lang are given, we'll guess the base_url as | |||||
| "//{lang}.{project}.org" (which is protocol-relative, becoming 'https' | |||||
| if 'useHTTPS' is True in config otherwise 'http'). If this is wrong, | |||||
| provide the correct base_url as an argument (in which case project and | |||||
| lang are ignored). Most wikis use "/w" as the script path (meaning the | |||||
| API is located at "{base_url}{script_path}/api.php" -> | |||||
| "//{lang}.{project}.org/w/api.php"), so this is the default. If your | |||||
| wiki is different, provide the script_path as an argument. The only | |||||
| other argument to Site() that we can't get from config files or by | |||||
| querying the wiki itself is SQL connection info, so provide a dict of | |||||
| kwargs as `sql` and Site will pass it to oursql.connect(**sql), | |||||
| allowing you to make queries with site.sql_query(). | |||||
| Returns True if the site was added successfully or False if the site is | |||||
| already in our sitesdb (this can be done purposefully to update old | |||||
| site info). Raises SiteNotFoundError if not enough information has | |||||
| been provided to identify the site (e.g. a project but not a lang). | |||||
| If only a project and a lang are given, we'll guess the *base_url* as | |||||
| ``"//{lang}.{project}.org"`` (which is protocol-relative, becoming | |||||
| ``"https"`` if *useHTTPS* is ``True`` in config otherwise ``"http"``). | |||||
| If this is wrong, provide the correct *base_url* as an argument (in | |||||
| which case project and lang are ignored). Most wikis use ``"/w"`` as | |||||
| the script path (meaning the API is located at | |||||
| ``"{base_url}{script_path}/api.php"`` -> | |||||
| ``"//{lang}.{project}.org/w/api.php"``), so this is the default. If | |||||
| your wiki is different, provide the script_path as an argument. The | |||||
| only other argument to :py:class:`~earwigbot.wiki.site.Site` that we | |||||
| can't get from config files or by querying the wiki itself is SQL | |||||
| connection info, so provide a dict of kwargs as *sql* and Site will | |||||
| pass it to :py:func:`oursql.connect(**sql) <oursql.connect>`, allowing | |||||
| you to make queries with :py:meth:`site.sql_query | |||||
| <earwigbot.wiki.site.Site.sql_query>`. | |||||
| Returns ``True`` if the site was added successfully or ``False`` if the | |||||
| site is already in our sitesdb (this can be done purposefully to update | |||||
| old site info). Raises :py:exc:`~earwigbot.exception.SiteNotFoundError` | |||||
| if not enough information has been provided to identify the site (e.g. | |||||
| a *project* but not a *lang*). | |||||
| """ | """ | ||||
| if not base_url: | if not base_url: | ||||
| if not project or not lang: | if not project or not lang: | ||||
| @@ -359,12 +367,12 @@ class SitesDB(object): | |||||
| def remove_site(self, name=None, project=None, lang=None): | def remove_site(self, name=None, project=None, lang=None): | ||||
| """Remove a site from the sitesdb. | """Remove a site from the sitesdb. | ||||
| Returns True if the site was removed successfully or False if the site | |||||
| was not in our sitesdb originally. If all three args (name, project, | |||||
| and lang) are given, we'll first try 'name' and then try the latter two | |||||
| if 'name' wasn't found in the database. Raises TypeError if a project | |||||
| was given but not a language, or vice versa. Will create an empty | |||||
| sitesdb if none was found. | |||||
| Returns ``True`` if the site was removed successfully or ``False`` if | |||||
| the site was not in our sitesdb originally. If all three args (*name*, | |||||
| *project*, and *lang*) are given, we'll first try *name* and then try | |||||
| the latter two if *name* wasn't found in the database. Raises | |||||
| :py:exc:`TypeError` if a project was given but not a language, or vice | |||||
| versa. Will create an empty sitesdb if none was found. | |||||
| """ | """ | ||||
| # Someone specified a project without a lang, or vice versa: | # Someone specified a project without a lang, or vice versa: | ||||
| if (project and not lang) or (not project and lang): | if (project and not lang) or (not project and lang): | ||||
| @@ -30,28 +30,33 @@ __all__ = ["User"] | |||||
| class User(object): | class User(object): | ||||
| """ | """ | ||||
| EarwigBot's Wiki Toolset: User Class | |||||
| **EarwigBot's Wiki Toolset: User Class** | |||||
| Represents a User on a given Site. Has methods for getting a bunch of | |||||
| information about the user, such as editcount and user rights, methods for | |||||
| returning the user's userpage and talkpage, etc. | |||||
| Represents a user on a given :py:class:`~earwigbot.wiki.site.Site`. Has | |||||
| methods for getting a bunch of information about the user, such as | |||||
| editcount and user rights, methods for returning the user's userpage and | |||||
| talkpage, etc. | |||||
| Attributes: | Attributes: | ||||
| name -- the user's username | |||||
| exists -- True if the user exists, or False if they do not | |||||
| userid -- an integer ID representing the user | |||||
| blockinfo -- information about any current blocks on the user | |||||
| groups -- a list of the user's groups | |||||
| rights -- a list of the user's rights | |||||
| editcount -- the number of edits made by the user | |||||
| registration -- the time the user registered as a time.struct_time | |||||
| emailable -- True if you can email the user, False if you cannot | |||||
| gender -- the user's gender ("male", "female", or "unknown") | |||||
| - :py:attr:`name`: the user's username | |||||
| - :py:attr:`exists`: ``True`` if the user exists, else ``False`` | |||||
| - :py:attr:`userid`: an integer ID representing the user | |||||
| - :py:attr:`blockinfo`: information about any current blocks on the user | |||||
| - :py:attr:`groups`: a list of the user's groups | |||||
| - :py:attr:`rights`: a list of the user's rights | |||||
| - :py:attr:`editcount`: the number of edits made by the user | |||||
| - :py:attr:`registration`: the time the user registered | |||||
| - :py:attr:`emailable`: ``True`` if you can email the user, or ``False`` | |||||
| - :py:attr:`gender`: the user's gender ("male"/"female"/"unknown") | |||||
| Public methods: | Public methods: | ||||
| reload -- forcibly reload the user's attributes | |||||
| get_userpage -- returns a Page object representing the user's userpage | |||||
| get_talkpage -- returns a Page object representing the user's talkpage | |||||
| - :py:meth:`reload`: forcibly reloads the user's attributes | |||||
| - :py:meth:`get_userpage`: returns a Page object representing the user's | |||||
| userpage | |||||
| - :py:meth:`get_talkpage`: returns a Page object representing the user's | |||||
| talkpage | |||||
| """ | """ | ||||
| def __init__(self, site, name): | def __init__(self, site, name): | ||||
| @@ -71,26 +76,25 @@ class User(object): | |||||
| self._name = name | self._name = name | ||||
| def __repr__(self): | def __repr__(self): | ||||
| """Returns the canonical string representation of the User.""" | |||||
| """Return the canonical string representation of the User.""" | |||||
| return "User(name={0!r}, site={1!r})".format(self._name, self._site) | return "User(name={0!r}, site={1!r})".format(self._name, self._site) | ||||
| def __str__(self): | def __str__(self): | ||||
| """Returns a nice string representation of the User.""" | |||||
| return '<User "{0}" of {1}>'.format(self.name(), str(self._site)) | |||||
| """Return a nice string representation of the User.""" | |||||
| return '<User "{0}" of {1}>'.format(self._name, str(self._site)) | |||||
| def _get_attribute(self, attr, force): | |||||
| def _get_attribute(self, attr): | |||||
| """Internally used to get an attribute by name. | """Internally used to get an attribute by name. | ||||
| We'll call _load_attributes() to get this (and all other attributes) | We'll call _load_attributes() to get this (and all other attributes) | ||||
| from the API if it is not already defined. If `force` is True, we'll | |||||
| re-load them even if they've already been loaded. | |||||
| from the API if it is not already defined. | |||||
| Raises UserNotFoundError if a nonexistant user prevents us from | Raises UserNotFoundError if a nonexistant user prevents us from | ||||
| returning a certain attribute. | returning a certain attribute. | ||||
| """ | """ | ||||
| if not hasattr(self, attr) or force: | |||||
| if not hasattr(self, attr): | |||||
| self._load_attributes() | self._load_attributes() | ||||
| if self._exists is False: | |||||
| if not self._exists: | |||||
| e = "User '{0}' does not exist.".format(self._name) | e = "User '{0}' does not exist.".format(self._name) | ||||
| raise UserNotFoundError(e) | raise UserNotFoundError(e) | ||||
| return getattr(self, attr) | return getattr(self, attr) | ||||
| @@ -150,105 +154,118 @@ class User(object): | |||||
| self._gender = res["gender"] | self._gender = res["gender"] | ||||
| def name(self, force=False): | |||||
| """Returns the user's name. | |||||
| If `force` is True, we will load the name from the API and return that. | |||||
| This could potentially return a "normalized" version of the name - for | |||||
| example, without a "User:" prefix or without underscores. Unlike other | |||||
| attribute getters, this will never make an API query without `force`. | |||||
| @property | |||||
| def name(self): | |||||
| """The user's username. | |||||
| Note that if another attribute getter, like exists(), has already been | |||||
| called, then the username has already been normalized. | |||||
| This will never make an API query on its own, but if one has already | |||||
| been made by the time this is retrieved, the username may have been | |||||
| "normalized" from the original input to the constructor, converted into | |||||
| a Unicode object, with underscores removed, etc. | |||||
| """ | """ | ||||
| if force: | |||||
| self._load_attributes() | |||||
| return self._name | return self._name | ||||
| def exists(self, force=False): | |||||
| """Returns True if the user exists, or False if they do not. | |||||
| @property | |||||
| def exists(self): | |||||
| """``True`` if the user exists, or ``False`` if they do not. | |||||
| Makes an API query if `force` is True or if we haven't made one | |||||
| already. | |||||
| Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| if not hasattr(self, "_exists") or force: | |||||
| if not hasattr(self, "_exists"): | |||||
| self._load_attributes() | self._load_attributes() | ||||
| return self._exists | return self._exists | ||||
| def userid(self, force=False): | |||||
| """Returns an integer ID used by MediaWiki to represent the user. | |||||
| @property | |||||
| def userid(self): | |||||
| """An integer ID used by MediaWiki to represent the user. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_userid", force) | |||||
| return self._get_attribute("_userid") | |||||
| def blockinfo(self, force=False): | |||||
| """Returns information about a current block on the user. | |||||
| @property | |||||
| def blockinfo(self): | |||||
| """Information about any current blocks on the user. | |||||
| If the user is not blocked, returns False. If they are, returns a dict | |||||
| with three keys: "by" is the blocker's username, "reason" is the reason | |||||
| why they were blocked, and "expiry" is when the block expires. | |||||
| If the user is not blocked, returns ``False``. If they are, returns a | |||||
| dict with three keys: ``"by"`` is the blocker's username, ``"reason"`` | |||||
| is the reason why they were blocked, and ``"expiry"`` is when the block | |||||
| expires. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_blockinfo", force) | return self._get_attribute("_blockinfo", force) | ||||
| def groups(self, force=False): | |||||
| """Returns a list of groups this user is in, including "*". | |||||
| @property | |||||
| def groups(self): | |||||
| """A list of groups this user is in, including ``"*"``. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_groups", force) | return self._get_attribute("_groups", force) | ||||
| def rights(self, force=False): | |||||
| """Returns a list of this user's rights. | |||||
| @property | |||||
| def rights(self): | |||||
| """A list of this user's rights. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_rights", force) | return self._get_attribute("_rights", force) | ||||
| def editcount(self, force=False): | |||||
| @property | |||||
| def editcount(self): | |||||
| """Returns the number of edits made by the user. | """Returns the number of edits made by the user. | ||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_editcount", force) | return self._get_attribute("_editcount", force) | ||||
| def registration(self, force=False): | |||||
| """Returns the time the user registered as a time.struct_time object. | |||||
| @property | |||||
| def registration(self): | |||||
| """The time the user registered as a :py:class:`time.struct_time`. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_registration", force) | return self._get_attribute("_registration", force) | ||||
| def emailable(self, force=False): | |||||
| """Returns True if the user can be emailed, or False if they cannot. | |||||
| @property | |||||
| def emailable(self): | |||||
| """``True`` if the user can be emailed, or ``False`` if they cannot. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_emailable", force) | return self._get_attribute("_emailable", force) | ||||
| def gender(self, force=False): | |||||
| """Returns the user's gender. | |||||
| @property | |||||
| def gender(self): | |||||
| """The user's gender. | |||||
| Can return either "male", "female", or "unknown", if they did not | |||||
| specify it. | |||||
| Can return either ``"male"``, ``"female"``, or ``"unknown"``, if they | |||||
| did not specify it. | |||||
| Raises UserNotFoundError if the user does not exist. Makes an API query | |||||
| if `force` is True or if we haven't made one already. | |||||
| Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user | |||||
| does not exist. Makes an API query only if we haven't made one already. | |||||
| """ | """ | ||||
| return self._get_attribute("_gender", force) | return self._get_attribute("_gender", force) | ||||
| def reload(self): | |||||
| """Forcibly reload the user's attributes. | |||||
| Emphasis on *reload*: this is only necessary if there is reason to | |||||
| believe they have changed. | |||||
| """ | |||||
| self._load_attributes() | |||||
| def get_userpage(self): | def get_userpage(self): | ||||
| """Returns a Page object representing the user's userpage. | |||||
| """Return a Page object representing the user's userpage. | |||||
| No checks are made to see if it exists or not. Proper site namespace | No checks are made to see if it exists or not. Proper site namespace | ||||
| conventions are followed. | conventions are followed. | ||||
| """ | """ | ||||
| @@ -257,8 +274,8 @@ class User(object): | |||||
| return Page(self._site, pagename) | return Page(self._site, pagename) | ||||
| def get_talkpage(self): | def get_talkpage(self): | ||||
| """Returns a Page object representing the user's talkpage. | |||||
| """Return a Page object representing the user's talkpage. | |||||
| No checks are made to see if it exists or not. Proper site namespace | No checks are made to see if it exists or not. Proper site namespace | ||||
| conventions are followed. | conventions are followed. | ||||
| """ | """ | ||||