jenkins-bot has submitted this change and it was merged.
Change subject: Make arguments consistent across .protect() methods ......................................................................
Make arguments consistent across .protect() methods
Page.protect(): - use a single 'protections' argument as in APISite.protect() although it defaults to 'None' it should be supplied. - use **kwargs to pass 'expiry' and 'cascade' to APISite.protect() - deprecated the 'edit', 'move', 'create' and 'upload' arguments they will be translated into 'protections' but ONLY if it was set to None (which is the case in exisiting scripts). - deprecated the 'unprotect' argument (not present in the API)
APISite.protect(): - rename the 'summary' argument into 'reason' for consistency with the Page method and the API - use **kwargs to pass 'cascade' directly to Request()
Logic of 'edit', 'move', 'create' and 'upload': - If protections is not None, it will warn for each value which is not None and ignores them - If protections is None, it'll create protections dict from them. It will also warn for each value which is not None. - 'edit' and 'move' are now default to False, so it warns only if they are set explicitly in conjunction with an existing protections dict. It'll also warn when they are False and protections is None.
APISite: - .protection_types(): return a set of available protection types - .protection_levels(): return a set of available protection levels - ._add_siteinfo(): queries additional siteinfo - .getmagicwords(): uses _add_siteinfo()
Page.applicable_protections(): - return a set of protections applicable to that page, but this is mostly hardcoded. But a future api.php update might fix it.
Change-Id: Ifc20770b7bbae8c86b920f8376e27d0bff47d2a3 --- M pywikibot/page.py M pywikibot/site.py 2 files changed, 162 insertions(+), 56 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py index cf6f367..1450642 100644 --- a/pywikibot/page.py +++ b/pywikibot/page.py @@ -824,6 +824,30 @@ """ return self.site.page_restrictions(self)
+ def applicable_protections(self): + """ + Return the protection types allowed for that page. + + If the page doesn't exists it only returns "create". Otherwise it + returns all protection types provided by the site, except "create". + It also removes "upload" if that page is not in the File namespace. + + It is possible, that it returns an empty set, but only if original + protection types were removed. + + @return: set of unicode + """ + # Currently hard coded, but a future API update might allow us to + # properly determine the applicable protection types + p_types = set(self.site.protection_types()) + if not self.exists(): + return set(['create']) if 'create' in p_types else set() + else: + p_types.remove('create') # no existing page allows that + if not self.isImage(): # only file pages allow upload + p_types.remove('upload') + return p_types + def canBeEdited(self): """Determine whether the page may be edited.
@@ -1557,60 +1581,76 @@ return self.site.undelete(self, comment)
@deprecate_arg("throttle", None) - def protect(self, edit='sysop', move='sysop', create=None, upload=None, - unprotect=False, reason=None, prompt=True, expiry=None): + def protect(self, edit=False, move=False, create=None, upload=None, + unprotect=False, reason=None, prompt=True, protections=None, + **kwargs): """(Un)protect a wiki page. Requires administrator status.
Valid protection levels (in MediaWiki 1.12) are '' (equivalent to 'none'), 'autoconfirmed', and 'sysop'. If None is given, however, that protection will be skipped.
- @param edit: Level of edit protection - @param move: Level of move protection - @param create: Level of create protection - @param upload: Level of upload protection - @param unprotect: If true, unprotect page editing and moving - (equivalent to set both edit and move to '') - @param reason: Edit summary. - @param prompt: If true, ask user for confirmation. - @param expiry: When the block should expire. This expiry will be - applied to all protections. - None, 'infinite', 'indefinite', 'never', and '' mean no expiry. - @type expiry: pywikibot.Timestamp, string in GNU timestamp format - (including ISO 8601). + @param protections: A dict mapping type of protection to protection + level of that type. + @type protections: dict + @param reason: Reason for the action + @type reason: basestring + @param prompt: Whether to ask user for confirmation + @type prompt: bool """ - if reason is None: - if unprotect: - un = u'un' + def deprecated(value, arg_name): + # if protections was set and value is None, don't interpret that + # argument. But otherwise warn that the parameter was set + # (even implicit) + if called_using_deprecated_arg: + if value is False: # explicit test for False (don't use not) + value = "sysop" + if value == "none": # 'none' doesn't seem do be accepted + value = "" + if value is not None: # empty string is allowed + protections[arg_name] = value + pywikibot.bot.warning(u'"protections" argument of ' + 'protect() replaces "{}".'.format(arg_name)) else: - un = u'' - pywikibot.output(u'Preparing to %sprotect %s.' - % (un, self.title(asLink=True))) + if value: + pywikibot.bot.warning(u'"protections" argument of ' + 'protect() replaces "{}"; cannot ' + 'use both.'.format(arg_name)) + + # buffer that, because it might get changed + called_using_deprecated_arg = protections is None + if called_using_deprecated_arg: + protections = {} + deprecated(edit, "edit") + deprecated(move, "move") + deprecated(create, "create") + deprecated(upload, "upload") + + if reason is None: + pywikibot.output(u'Preparing to protection change of %s.' + % (self.title(asLink=True))) reason = pywikibot.input(u'Please enter a reason for the action:') if unprotect: - edit = move = "" - # Apply to only edit and move for backward compatibility. - # To unprotect article creation, for example, - # create must be set to '' and the rest must be None + pywikibot.bot.warning(u'"unprotect" argument of protect() is ' + 'deprecated') + protections = dict( + [(p_type, "") for p_type in self.applicable_protections()]) answer = 'y' + if prompt: + pywikibot.bot.warning(u'"prompt" argument of protect() is ' + 'deprecated') if prompt and not hasattr(self.site, '_noProtectPrompt'): answer = pywikibot.inputChoice( u'Do you want to change the protection level of %s?' % self.title(asLink=True, forceInterwiki=True), ['Yes', 'No', 'All'], - ['Y', 'N', 'A'], + ['y', 'N', 'a'], 'N') - if answer in ['a', 'A']: + if answer == 'a': answer = 'y' self.site._noProtectPrompt = True - if answer in ['y', 'Y']: - protections = { - 'edit': edit, - 'move': move, - 'create': create, - 'upload': upload, - } - return self.site.protect(self, protections, reason, expiry) + if answer == 'y': + return self.site.protect(self, protections, reason, **kwargs)
def change_category(self, oldCat, newCat, comment=None, sortKey=None, inPlace=True): diff --git a/pywikibot/site.py b/pywikibot/site.py index b70ad2d..25bfa07 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -1126,23 +1126,11 @@ def getmagicwords(self, word): """Return list of localized "word" magic words for the site.""" if not hasattr(self, "_magicwords"): - sirequest = api.CachedRequest( - expiry=config.API_config_expiry, - site=self, - action="query", - meta="siteinfo", - siprop="magicwords" - ) try: - sidata = sirequest.submit() - assert 'query' in sidata, \ - "API siteinfo response lacks 'query' key" - sidata = sidata['query'] - assert 'magicwords' in sidata, \ - "API siteinfo response lacks 'magicwords' key" + # don't cache in _siteinfo, because we cache it in _magicwords + magicwords = self._add_siteinfo("magicwords", False) self._magicwords = dict((item["name"], item["aliases"]) - for item in sidata["magicwords"]) - + for item in magicwords) except api.APIError: # hack for older sites that don't support 1.13 properties # probably should delete if we're not going to support pre-1.13 @@ -1186,6 +1174,44 @@ def pagename2codes(self, default=True): """Return list of localized PAGENAMEE tags for the site.""" return self.getmagicwords("pagenamee") + + def _add_siteinfo(self, prop, cache, force=False): + """ + Retrieve additional siteinfo and optionally cache it. + + Queries the site and returns the properties. It can cache the value + so that future queries will access the cache. With C{force} set to + True it won't access the cache but it can still cache the value. If + the property doesn't exists it returns None. + + @param prop: The property name of the siteinfo. + @type prop: str + @param cache: Should this be cached? + @type cache: bool + @param force: Should the cache be skipped? + @type force: bool + @return: The properties of the site. + @rtype: various (depends on prop) + """ + if not hasattr(self, '_siteinfo'): + force = True # if it doesn't exists there won't be a cache + if cache: # but only initialise cache if that is requested + self._getsiteinfo() + if not force and prop in self._siteinfo: + return self._siteinfo[prop] + data = pywikibot.data.api.CachedRequest( + expiry=0 if force else pywikibot.config.API_config_expiry, + site=self, + action='query', + meta='siteinfo', + siprop=prop).submit() + try: + prop_data = data['query'][prop] + except KeyError: + prop_data = None + if cache: + self._siteinfo[prop] = prop_data + return prop_data
def _getsiteinfo(self, force=False): """Retrieve siteinfo and namespaces from site.""" @@ -3202,8 +3228,45 @@ "protect-invalidlevel": "Invalid protection level" }
+ def protection_types(self): + """ + Return the protection types available on this site. + + With MediaWiki version 1.23 protection types can be retrieved. To + support older wikis, the default protection types 'create', 'edit', + 'move' and 'upload' are returned. + + @return protection types available + @rtype: set of unicode instances + """ + # implemented in b73b5883d486db0e9278ef16733551f28d9e096d + restrictions = self._add_siteinfo('restrictions', True) + if restrictions is None or 'types' not in restrictions: + return set([u'create', u'edit', u'move', u'upload']) + else: + return set(restrictions['types']) + + def protection_levels(self): + """ + Return the protection levels available on this site. + + With MediaWiki version 1.23 protection levels can be retrieved. To + support older wikis, the default protection levels '', 'autoconfirmed', + and 'sysop' are returned. + + @return protection types available + @rtype: set of unicode instances + """ + # implemented in b73b5883d486db0e9278ef16733551f28d9e096d + restrictions = self._add_siteinfo('restrictions', True) + if restrictions is None or 'levels' not in restrictions: + return set([u'', u'autoconfirmed', u'sysop']) + else: + return set(restrictions['levels']) + @must_be(group='sysop') - def protect(self, page, protections, summary, expiry=None): + @deprecate_arg("summary", "reason") + def protect(self, page, protections, reason, expiry=None, **kwargs): """(Un)protect a wiki page. Requires administrator status.
@param protections: A dict mapping type of protection to protection @@ -3211,22 +3274,25 @@ 'create', and 'upload'. Valid protection levels (in MediaWiki 1.12) are '' (equivalent to 'none'), 'autoconfirmed', and 'sysop'. If None is given, however, that protection will be skipped. - @param summary: Edit summary. + @type protections: dict + @param reason: Reason for the action + @type reason: basestring @param expiry: When the block should expire. This expiry will be applied to all protections. If None, 'infinite', 'indefinite', 'never', or '' is given, there is no expiry. @type expiry: pywikibot.Timestamp, string in GNU timestamp format (including ISO 8601). """ - token = self.token(page, "protect") + token = self.token(page, 'protect') self.lock_page(page)
- protectList = [type + '=' + level for type, level in protections.items() + protectList = [ptype + '=' + level for ptype, level in protections.items() if level is not None] - req = api.Request(site=self, action="protect", token=token, + req = api.Request(site=self, action='protect', token=token, title=page.title(withSection=False), protections=protectList, - reason=summary) + reason=reason, + **kwargs) if isinstance(expiry, pywikibot.Timestamp): expiry = expiry.toISOformat() if expiry: