diff --git a/mwclient/client.py b/mwclient/client.py index 9fdd2c9d8913a585299eed385769c97f8715a1b1..6dc7235e694e32a41f4389da7068a6d1f53e47eb 100644 --- a/mwclient/client.py +++ b/mwclient/client.py @@ -238,6 +238,7 @@ class Site(object): self.wait(token) def raw_api(self, action, *args, **kwargs): + """Sends a call to the API.""" kwargs['action'] = action kwargs['format'] = 'json' data = self._query_string(*args, **kwargs) @@ -250,6 +251,7 @@ class Site(object): raise def raw_index(self, action, *args, **kwargs): + """Sends a call to index.php rather than the API.""" kwargs['action'] = action kwargs['maxlag'] = self.max_lag data = self._query_string(*args, **kwargs) @@ -259,6 +261,7 @@ class Site(object): token = WaitToken() self.wait_tokens[token] = (0, args) return token + def wait(self, token, min_wait = 0): retry, args = self.wait_tokens[token] self.wait_tokens[token] = (retry + 1, args) @@ -290,6 +293,7 @@ class Site(object): # Actions def email(self, user, text, subject, cc = False): + """Sends email to a specified user on the wiki.""" #TODO: Use api! postdata = {} postdata['wpSubject'] = subject @@ -308,6 +312,7 @@ class Site(object): def login(self, username = None, password = None, cookies = None, domain = None): + """Login to the wiki.""" if self.initialized: self.require(1, 10) if username and password: @@ -349,6 +354,7 @@ class Site(object): def upload(self, file = None, filename = None, description = '', ignore = False, file_size = None, url = None, session_key = None): + """Upload a file to the wiki.""" if self.version[:2] < (1, 16): return compatibility.old_upload(self, file = file, filename = filename, description = description, ignore = ignore, @@ -422,6 +428,7 @@ class Site(object): def allpages(self, start = None, prefix = None, namespace = '0', filterredir = 'all', minsize = None, maxsize = None, prtype = None, prlevel = None, limit = None, dir = 'ascending', filterlanglinks = 'all', generator = True): + """Retrieve all pages on the wiki as a generator.""" self.require(1, 9) pfx = listing.List.get_prefix('ap', generator) @@ -435,6 +442,7 @@ class Site(object): def alllinks(self, start = None, prefix = None, unique = False, prop = 'title', namespace = '0', limit = None, generator = True): + """Retrieve a list of all links on the wiki as a generator.""" self.require(1, 11) pfx = listing.List.get_prefix('al', generator) @@ -444,6 +452,7 @@ class Site(object): return listing.List.get_list(generator)(self, 'alllinks', 'al', limit = limit, return_values = 'title', **kwargs) def allcategories(self, start = None, prefix = None, dir = 'ascending', limit = None, generator = True): + """Retrieve all categories on the wiki as a generator.""" self.require(1, 12) pfx = listing.List.get_prefix('ac', generator) @@ -451,18 +460,35 @@ class Site(object): return listing.List.get_list(generator)(self, 'allcategories', 'ac', limit = limit, **kwargs) def allusers(self, start = None, prefix = None, group = None, prop = None, limit = None): + """Retrieve all users on the wiki as a generator.""" self.require(1, 11) kwargs = dict(listing.List.generate_kwargs('au', ('from', start), prefix = prefix, group = group, prop = prop)) return listing.List(self, 'allusers', 'au', limit = limit, **kwargs) + def blocks(self, start = None, end = None, dir = 'older', ids = None, users = None, limit = None, prop = 'id|user|by|timestamp|expiry|reason|flags'): + """Retrieve blocks as a generator. + + Each block is a dictionary containing: + - user: the username or IP address of the user + - id: the ID of the block + - timestamp: when the block was added + - expiry: when the block runs out (infinity for indefinite blocks) + - reason: the reason they are blocked + - allowusertalk: key is present (empty string) if the user is allowed to edit their user talk page + - by: the administrator who blocked the user + - nocreate: key is present (empty string) if the user's ability to create accounts has been disabled. + + """ + self.require(1, 12) # TODO: Fix. Fix what? kwargs = dict(listing.List.generate_kwargs('bk', start = start, end = end, dir = dir, users = users, prop = prop)) return listing.List(self, 'blocks', 'bk', limit = limit, **kwargs) + def deletedrevisions(self, start = None, end = None, dir = 'older', namespace = None, limit = None, prop = 'user|comment'): # TODO: Fix @@ -471,12 +497,31 @@ class Site(object): kwargs = dict(listing.List.generate_kwargs('dr', start = start, end = end, dir = dir, namespace = namespace, prop = prop)) return listing.List(self, 'deletedrevs', 'dr', limit = limit, **kwargs) + def exturlusage(self, query, prop = None, protocol = 'http', namespace = None, limit = None): + """Retrieves list of pages that link to a particular domain or URL as a generator. + + This API call mirrors the Special:LinkSearch function on-wiki. + + Query can be a domain like 'bbc.co.uk'. Wildcards can be used, e.g. '*.bbc.co.uk'. + Alternatively, a query can contain a full domain name and some or all of a URL: + e.g. '*.wikipedia.org/wiki/*' + + See <https://meta.wikimedia.org/wiki/Help:Linksearch> for details. + + The generator returns dictionaries containing three keys: + - url: the URL linked to. + - ns: namespace of the wiki page + - pageid: the ID of the wiki page + - title: the page title. + + """ self.require(1, 11) kwargs = dict(listing.List.generate_kwargs('eu', query = query, prop = prop, protocol = protocol, namespace = namespace)) return listing.List(self, 'exturlusage', 'eu', limit = limit, **kwargs) + def logevents(self, type = None, prop = None, start = None, end = None, dir = 'older', user = None, title = None, limit = None): self.require(1, 9) @@ -484,8 +529,17 @@ class Site(object): kwargs = dict(listing.List.generate_kwargs('le', prop = prop, type = type, start = start, end = end, dir = dir, user = user, title = title)) return listing.List(self, 'logevents', 'le', limit = limit, **kwargs) + # def protectedtitles requires 1.15 def random(self, namespace, limit = 20): + """Retrieves a generator of random page from a particular namespace. + + limit specifies the number of random articles retrieved. + namespace is a namespace identifier integer. + + Generator contains dictionary with namespace, page ID and title. + + """ self.require(1, 12) kwargs = dict(listing.List.generate_kwargs('rn', namespace = namespace)) @@ -498,12 +552,14 @@ class Site(object): kwargs = dict(listing.List.generate_kwargs('rc', start = start, end = end, dir = dir, namespace = namespace, prop = prop, show = show, type = type)) return listing.List(self, 'recentchanges', 'rc', limit = limit, **kwargs) + def search(self, search, namespace = '0', what = 'title', redirects = False, limit = None): self.require(1, 11) kwargs = dict(listing.List.generate_kwargs('sr', search = search, namespace = namespace, what = what)) if redirects: kwargs['srredirects'] = '1' return listing.List(self, 'search', 'sr', limit = limit, **kwargs) + def usercontributions(self, user, start = None, end = None, dir = 'older', namespace = None, prop = None, show = None, limit = None): self.require(1, 9) @@ -511,6 +567,7 @@ class Site(object): kwargs = dict(listing.List.generate_kwargs('uc', user = user, start = start, end = end, dir = dir, namespace = namespace, prop = prop, show = show)) return listing.List(self, 'usercontribs', 'uc', limit = limit, **kwargs) + def users(self, users, prop = 'blockinfo|groups|editcount'): self.require(1, 12) @@ -526,6 +583,7 @@ class Site(object): return listing.List(self, 'watchlist', 'wl', limit = limit, **kwargs) def expandtemplates(self, text, title = None, generatexml = False): + """Takes wikitext (text) and expands templates.""" self.require(1, 11) kwargs = {} diff --git a/mwclient/listing.py b/mwclient/listing.py index 363f8d74d5e09451ec6bac566395006183b995c1..3c510959b5bf0f62bc69b828cf3e15b1e41b4bac 100644 --- a/mwclient/listing.py +++ b/mwclient/listing.py @@ -61,6 +61,7 @@ class List(object): self.args.update(data['query-continue'][self.list_name]) else: self.last = True + def set_iter(self, data): if self.result_member not in data['query']: self._iter = iter(xrange(0)) @@ -79,12 +80,14 @@ class List(object): for key, value in kwargs.iteritems(): if value != None: yield _prefix + key, value + @staticmethod def get_prefix(prefix, generator = False): if generator: return 'g' + prefix else: return prefix + @staticmethod def get_list(generator = False): if generator: @@ -131,8 +134,10 @@ class Category(page.Page, GeneratorList): 1, 12, raise_error = False), prefix = 'gcm'), )) if namespace: kwargs['gcmnamespace'] = namespace GeneratorList.__init__(self, site, 'categorymembers', 'cm', **kwargs) + def __repr__(self): return "<Category object '%s' for %s>" % (self.name.encode('utf-8'), self.site) + def members(self, prop = 'ids|title', namespace = None, sort = 'sortkey', dir = 'asc', start = None, end = None, generator = True): prefix = self.get_prefix('cm', generator) @@ -154,6 +159,7 @@ class PageList(GeneratorList): def __getitem__(self, name): return self.get(name, None) + def get(self, name, info = ()): if self.namespace == 14: return Category(self.site, self.site.namespaces[14] + ':' + name, info) @@ -188,6 +194,7 @@ class PageProperty(List): List.__init__(self, page.site, prop, prefix, titles = page.name, *args, **kwargs) self.page = page self.generator = 'prop' + def set_iter(self, data): for page in data['query']['pages'].itervalues(): if page['title'] == self.page.name: @@ -206,4 +213,4 @@ class RevisionsIterator(PageProperty): if 'rvstartid' in self.args and 'rvstart' in self.args: del self.args['rvstart'] return PageProperty.load_chunk(self) - \ No newline at end of file + diff --git a/mwclient/page.py b/mwclient/page.py index 843d301b8d7e623329c75685ace0cf5fa6db6399..a32567e82ee7bab578995f8b889960965f7be160 100644 --- a/mwclient/page.py +++ b/mwclient/page.py @@ -44,6 +44,7 @@ class Page(object): def __repr__(self): return "<Page object '%s' for %s>" % (self.name.encode('utf-8'), self.site) + def __unicode__(self): return self.name @@ -52,6 +53,7 @@ class Page(object): if title[0] == ':': title = title[1:] return title[title.find(':') + 1:] + @staticmethod def normalize_title(title): # TODO: Make site dependent @@ -92,6 +94,11 @@ class Page(object): return u'' def edit(self, section = None, readonly = False): + """Returns wikitext for a specified section or for the whole page. + + Retrieves the latest edit. + + """ if not self.can('read'): raise errors.InsufficientPermission(self) if not self.exists: @@ -109,6 +116,7 @@ class Page(object): return self.text def save(self, text = u'', summary = u'', minor = False, bot = True, **kwargs): + """Save text of page.""" if not self.site.logged_in and self.site.force_login: # Should we really check for this? raise errors.LoginError(self.site) @@ -174,6 +182,15 @@ class Page(object): return u'' def move(self, new_title, reason = '', move_talk = True, no_redirect = False): + """Move (rename) page to new_title. + + If user account is an administrator, specify no_direct as True to not + leave a redirect. + + If user does not have permission to move page, an InsufficientPermission + exception is raised. + + """ if not self.can('move'): raise errors.InsufficientPermission(self) if not self.site.writeapi: @@ -189,6 +206,12 @@ class Page(object): def delete(self, reason = '', watch = False, unwatch = False, oldimage = False): + """Delete page. + + If user does not have permission to delete page, an InsufficientPermission + exception is raised. + + """ if not self.can('delete'): raise errors.InsufficientPermission(self) if not self.site.writeapi: @@ -204,6 +227,10 @@ class Page(object): return result['delete'] def purge(self): + """Purge server-side cache of page. This will re-render templates and other + dynamic content. + + """ self.site.raw_index('purge', title = self.name) # def watch: requires 1.14 @@ -219,6 +246,7 @@ class Page(object): kwargs[compatibility.title(prefix, self.site.require(1, 11, raise_error = False))] = self.name return listing.List.get_list(generator)(self.site, 'backlinks', 'bl', limit = limit, return_values = 'title', **kwargs) + def categories(self, generator = True): self.site.require(1, 11) if generator: @@ -226,6 +254,7 @@ class Page(object): else: # TODO: return sortkey if wanted return listing.PageProperty(self, 'categories', 'cl', return_values = 'title') + def embeddedin(self, namespace = None, filterredir = 'all', redirect = False, limit = None, generator = True): self.site.require(1, 9) # Fix title for < 1.11 !! @@ -236,18 +265,22 @@ class Page(object): kwargs[compatibility.title(prefix, self.site.require(1, 11, raise_error = False))] = self.name return listing.List.get_list(generator)(self.site, 'embeddedin', 'ei', limit = limit, return_values = 'title', **kwargs) + def extlinks(self): self.site.require(1, 11) return listing.PageProperty(self, 'extlinks', 'el', return_values = '*') + def images(self, generator = True): self.site.require(1, 9) if generator: return listing.PagePropertyGenerator(self, 'images', '') else: return listing.PageProperty(self, 'images', '', return_values = 'title') + def langlinks(self): self.site.require(1, 9) return listing.PageProperty(self, 'langlinks', 'll', return_values = ('lang', '*')) + def links(self, namespace = None, generator = True): self.site.require(1, 9) kwargs = dict(listing.List.generate_kwargs('pl', namespace = namespace)) @@ -267,6 +300,7 @@ class Page(object): if expandtemplates: kwargs['rvexpandtemplates'] = '1' return listing.RevisionsIterator(self, 'revisions', 'rv', limit = limit, **kwargs) + def templates(self, namespace = None, generator = True): self.site.require(1, 8) kwargs = dict(listing.List.generate_kwargs('tl', namespace = namespace)) @@ -287,6 +321,7 @@ class Image(Page): def imagehistory(self): return listing.PageProperty(self, 'imageinfo', 'ii', iiprop = compatibility.iiprop(self.site.version)) + def imageusage(self, namespace = None, filterredir = 'all', redirect = False, limit = None, generator = True): self.site.require(1, 11) @@ -297,6 +332,7 @@ class Image(Page): if redirect: kwargs['%sredirect' % prefix] = '1' return listing.List.get_list(generator)(self.site, 'imageusage', 'iu', limit = limit, return_values = 'title', **kwargs) + def duplicatefiles(self, limit = None): self.require(1, 14) return listing.PageProperty(self, 'duplicatefiles', 'df',