From 60da4423e2181bbb090b04aa24f7c1e3c7717af0 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 6 May 2012 00:14:33 -0400 Subject: [PATCH] Property-ify User; docstring cleanup --- earwigbot/commands/afc_report.py | 2 +- earwigbot/commands/editcount.py | 4 +- earwigbot/commands/registration.py | 9 +- earwigbot/commands/rights.py | 2 +- earwigbot/tasks/__init__.py | 2 +- earwigbot/tasks/afc_statistics.py | 2 +- earwigbot/wiki/category.py | 6 +- earwigbot/wiki/page.py | 18 ++-- earwigbot/wiki/site.py | 8 +- earwigbot/wiki/sitesdb.py | 96 +++++++++++--------- earwigbot/wiki/user.py | 181 ++++++++++++++++++++----------------- 11 files changed, 177 insertions(+), 153 deletions(-) diff --git a/earwigbot/commands/afc_report.py b/earwigbot/commands/afc_report.py index 54a077b..6c3348d 100644 --- a/earwigbot/commands/afc_report.py +++ b/earwigbot/commands/afc_report.py @@ -78,7 +78,7 @@ class Command(BaseCommand): short = self.statistics.get_short_title(page.title) status = self.get_status(page) user = self.site.get_user(page.creator()) - user_name = user.name() + user_name = user.name user_url = user.get_talkpage().url msg1 = "AfC submission report for \x0302{0}\x0301 ({1}):" diff --git a/earwigbot/commands/editcount.py b/earwigbot/commands/editcount.py index 237d18d..13dad27 100644 --- a/earwigbot/commands/editcount.py +++ b/earwigbot/commands/editcount.py @@ -45,13 +45,13 @@ class Command(BaseCommand): user = site.get_user(name) try: - count = user.editcount() + count = user.editcount except wiki.UserNotFoundError: msg = "the user \x0302{0}\x0301 does not exist." self.reply(data, msg.format(name)) 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" msg = "\x0302{0}\x0301 has {1} edits ({2})." self.reply(data, msg.format(name, count, url.format(safe))) diff --git a/earwigbot/commands/registration.py b/earwigbot/commands/registration.py index a36bcab..913ca33 100644 --- a/earwigbot/commands/registration.py +++ b/earwigbot/commands/registration.py @@ -45,7 +45,7 @@ class Command(BaseCommand): user = site.get_user(name) try: - reg = user.registration() + reg = user.registration except wiki.UserNotFoundError: msg = "the user \x0302{0}\x0301 does not exist." 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) age = self.get_diff(time.mktime(reg), time.mktime(time.gmtime())) - g = user.gender() - if g == "male": + if user.gender == "male": gender = "He's" - elif g == "female": + elif user.gender == "female": gender = "She's" else: gender = "They're" - + msg = "\x0302{0}\x0301 registered on {1}. {2} {3} old." self.reply(data, msg.format(name, date, gender, age)) diff --git a/earwigbot/commands/rights.py b/earwigbot/commands/rights.py index 10c5137..1ccb1ca 100644 --- a/earwigbot/commands/rights.py +++ b/earwigbot/commands/rights.py @@ -43,7 +43,7 @@ class Command(BaseCommand): user = site.get_user(name) try: - rights = user.groups() + rights = user.groups except wiki.UserNotFoundError: msg = "the user \x0302{0}\x0301 does not exist." self.reply(data, msg.format(name)) diff --git a/earwigbot/tasks/__init__.py b/earwigbot/tasks/__init__.py index d830f1e..ba604b0 100644 --- a/earwigbot/tasks/__init__.py +++ b/earwigbot/tasks/__init__.py @@ -116,7 +116,7 @@ class BaseTask(object): except KeyError: return False 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)) page = site.get_page(title) diff --git a/earwigbot/tasks/afc_statistics.py b/earwigbot/tasks/afc_statistics.py index bf1a574..e29913c 100644 --- a/earwigbot/tasks/afc_statistics.py +++ b/earwigbot/tasks/afc_statistics.py @@ -718,7 +718,7 @@ class Task(BaseTask): if chart in [self.CHART_PEND, self.CHART_DRAFT] and s_user: submitter = self.site.get_user(s_user) try: - if submitter.blockinfo(): + if submitter.blockinfo: notes += "|nb=1" # Submitter is blocked except wiki.UserNotFoundError: # Likely an IP pass diff --git a/earwigbot/wiki/category.py b/earwigbot/wiki/category.py index 9b8490b..27dd999 100644 --- a/earwigbot/wiki/category.py +++ b/earwigbot/wiki/category.py @@ -43,12 +43,12 @@ class Category(Page): """ 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})" return res.format(self._title, self._follow_redirects, self._site) def __str__(self): - """Returns a nice string representation of the Category.""" + """Return a nice string representation of the Category.""" return ''.format(self.title, str(self._site)) def _get_members_via_sql(self, limit): @@ -87,7 +87,7 @@ class Category(Page): return [member["title"] for member in members] 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. Pages will be returned as tuples of ``(title, pageid)`` instead of just diff --git a/earwigbot/wiki/page.py b/earwigbot/wiki/page.py index e589afa..f6ea257 100644 --- a/earwigbot/wiki/page.py +++ b/earwigbot/wiki/page.py @@ -123,12 +123,12 @@ class Page(CopyrightMixin): self._is_talkpage = self._namespace % 2 == 1 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})" return res.format(self._title, self._follow_redirects, self._site) def __str__(self): - """Returns a nice string representation of the Page.""" + """Return a nice string representation of the Page.""" return ''.format(self.title, str(self._site)) def _assert_validity(self): @@ -157,7 +157,7 @@ class Page(CopyrightMixin): raise exceptions.PageNotFoundError(e) 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 passed to __init__() (perhaps indirectly passed by site.get_page()). @@ -180,7 +180,7 @@ class Page(CopyrightMixin): self._load_attributes() 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, ._protection, ._namespace, ._is_talkpage, ._creator, ._lastrevid, @@ -245,7 +245,7 @@ class Page(CopyrightMixin): pass 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 query and try to get content from that. Otherwise, we'll do an API @@ -557,7 +557,7 @@ class Page(CopyrightMixin): self._load_content() 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 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) 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 invalid or the page does not exist, respectively. @@ -675,7 +675,7 @@ class Page(CopyrightMixin): return self._site.get_user(self._creator) 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. If *minor* is ``True``, the edit will be marked as minor. If *bot* is @@ -690,7 +690,7 @@ class Page(CopyrightMixin): force=force) 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 instead of providing a summary, you provide a section title. diff --git a/earwigbot/wiki/site.py b/earwigbot/wiki/site.py index dbdb824..4b95e9c 100644 --- a/earwigbot/wiki/site.py +++ b/earwigbot/wiki/site.py @@ -160,7 +160,7 @@ class Site(object): self._login(login) def __repr__(self): - """Returns the canonical string representation of the Site.""" + """Return the canonical string representation of the Site.""" res = ", ".join(( "Site(name={_name!r}", "project={_project!r}", "lang={_lang!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__) def __str__(self): - """Returns a nice string representation of the Site.""" + """Return a nice string representation of the Site.""" res = "" return res.format(self.name, self.project, self.lang, self.domain) @@ -704,7 +704,7 @@ class Site(object): return Page(self, title, follow_redirects) 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 really just shorthand for :py:meth:`get_page("Category:" + catname) @@ -715,7 +715,7 @@ class Site(object): return Category(self, pagename, follow_redirects) 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 :py:class:`~earwigbot.wiki.user.User` object representing the currently diff --git a/earwigbot/wiki/sitesdb.py b/earwigbot/wiki/sitesdb.py index 7e7db1f..034d0a4 100644 --- a/earwigbot/wiki/sitesdb.py +++ b/earwigbot/wiki/sitesdb.py @@ -35,20 +35,23 @@ __all__ = ["SitesDB"] 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 - 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): @@ -157,7 +160,7 @@ class SitesDB(object): namespaces) 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 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. 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 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 - config.wiki["username"] and config.wiki["password"] if both are + ``config.wiki["username"]`` and ``config.wiki["password"]`` if both are defined. 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. """ # Someone specified a project without a lang, or vice versa: @@ -311,23 +315,27 @@ class SitesDB(object): script_path="/w", sql=None): """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) `, allowing + you to make queries with :py:meth:`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 project or not lang: @@ -359,12 +367,12 @@ class SitesDB(object): def remove_site(self, name=None, project=None, lang=None): """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: if (project and not lang) or (not project and lang): diff --git a/earwigbot/wiki/user.py b/earwigbot/wiki/user.py index 67c9567..b707acb 100644 --- a/earwigbot/wiki/user.py +++ b/earwigbot/wiki/user.py @@ -30,28 +30,33 @@ __all__ = ["User"] 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: - 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: - 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): @@ -71,26 +76,25 @@ class User(object): self._name = name 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) def __str__(self): - """Returns a nice string representation of the User.""" - return ''.format(self.name(), str(self._site)) + """Return a nice string representation of the User.""" + return ''.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. 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 returning a certain attribute. """ - if not hasattr(self, attr) or force: + if not hasattr(self, attr): self._load_attributes() - if self._exists is False: + if not self._exists: e = "User '{0}' does not exist.".format(self._name) raise UserNotFoundError(e) return getattr(self, attr) @@ -150,105 +154,118 @@ class User(object): 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 - 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() 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) - 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) - 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) - def editcount(self, force=False): + @property + def editcount(self): """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) - 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) - 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) - 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) + 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): - """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 conventions are followed. """ @@ -257,8 +274,8 @@ class User(object): return Page(self._site, pagename) 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 conventions are followed. """