13 def __init__(self, site, name, info=None, extra_properties={}):
14 if type(name) is type(self):
15 return self.__dict__.update(name.__dict__)
22 prop = 'info|' + '|'.join(extra_properties.iterkeys())
24 [extra_props.extend(extra_prop) for extra_prop in extra_properties.itervalues()]
30 info = self.site.api('query', prop=prop, pageids=name,
31 inprop='protection', *extra_props)
33 info = self.site.api('query', prop=prop, titles=name,
34 inprop='protection', *extra_props)
35 info = info['query']['pages'].itervalues().next()
38 self.namespace = info.get('ns', 0)
39 self.name = info.get('title', u'')
41 self.page_title = self.strip_namespace(self.name)
43 self.page_title = self.name
45 self.touched = client.parse_timestamp(info.get('touched', '0000-00-00T00:00:00Z'))
46 self.revision = info.get('lastrevid', 0)
47 self.exists = 'missing' not in info
48 self.length = info.get('length')
49 self.protection = dict([(i['type'], (i['level'], i['expiry'])) for i in info.get('protection', ()) if i])
50 self.redirect = 'redirect' in info
52 self.last_rev_time = None
55 def redirects_to(self):
56 """ Returns the redirect target page, or None if the page is not a redirect page."""
57 info = self.site.api('query', prop='pageprops', titles=self.name, redirects='')['query']
58 if 'redirects' in info:
59 for page in info['redirects']:
60 if page['from'] == self.name:
61 return Page(self.site, page['to'])
66 def resolve_redirect(self):
67 """ Returns the redirect target page, or the current page if it's not a redirect page."""
68 target_page = self.redirects_to()
69 if target_page is None:
75 return "<Page object '%s' for %s>" % (self.name.encode('utf-8'), self.site)
77 def __unicode__(self):
81 def strip_namespace(title):
84 return title[title.find(':') + 1:]
87 def normalize_title(title):
88 # TODO: Make site dependent
92 title = title[0].upper() + title[1:]
93 title = title.replace(' ', '_')
96 def can(self, action):
97 level = self.protection.get(action, (action, ))[0]
99 level = 'editprotected'
101 return level in self.site.rights
103 def get_token(self, type, force=False):
104 return self.site.get_token(type, force, title=self.name)
106 def get_expanded(self):
107 """Deprecated. Use page.text(expandtemplates=True) instead"""
108 warnings.warn("page.get_expanded() was deprecated in mwclient 0.7.0, use page.text(expandtemplates=True) instead.",
109 category=DeprecationWarning, stacklevel=2)
111 return self.text(expandtemplates=True)
113 def edit(self, *args, **kwargs):
114 """Deprecated. Use page.text() instead"""
115 warnings.warn("page.edit() was deprecated in mwclient 0.7.0, please use page.text() instead.",
116 category=DeprecationWarning, stacklevel=2)
117 return self.text(*args, **kwargs)
119 def text(self, section=None, expandtemplates=False):
121 Returns the current wikitext of the page, or of a specific section.
122 If the page does not exist, an empty string is returned.
125 - `section` : numbered section or `None` to get the whole page (default: `None`)
126 - `expandtemplates` : set to `True` to expand templates (default: `False`)
129 if not self.can('read'):
130 raise errors.InsufficientPermission(self)
133 if section is not None:
134 section = str(section)
136 revs = self.revisions(prop='content|timestamp', limit=1, section=section, expandtemplates=expandtemplates)
140 self.section = section
141 self.last_rev_time = rev['timestamp']
142 except StopIteration:
145 self.last_rev_time = None
146 if not expandtemplates:
147 self.edit_time = time.gmtime()
150 def save(self, text, summary=u'', minor=False, bot=True, section=None, **kwargs):
152 Update the text of a section or the whole page by performing an edit operation.
154 if not self.site.logged_in and self.site.force_login:
155 # Should we really check for this?
156 raise errors.LoginError(self.site, 'By default, mwclient protects you from accidentally ' +
157 'editing without being logged in. If you actually want to edit without '
158 'logging in, you can set force_login on the Site object to False.')
159 if self.site.blocked:
160 raise errors.UserBlocked(self.site.blocked)
161 if not self.can('edit'):
162 raise errors.ProtectedPageError(self)
165 section = self.section
167 if not self.site.writeapi:
168 raise errors.NoWriteApi(self)
174 data['notminor'] = '1'
175 if self.last_rev_time:
176 data['basetimestamp'] = time.strftime('%Y%m%d%H%M%S', self.last_rev_time)
178 data['starttimestamp'] = time.strftime('%Y%m%d%H%M%S', self.edit_time)
182 data['section'] = section
187 result = self.site.api('edit', title=self.name, text=text,
188 summary=summary, token=self.get_token('edit'),
190 if result['edit'].get('result').lower() == 'failure':
191 raise errors.EditError(self, result['edit'])
195 except errors.APIError, e:
196 if e.code == 'badtoken':
197 # Retry, but only once to avoid an infinite loop
198 self.get_token('edit', force=True)
201 except errors.APIError, e:
202 self.handle_edit_error(e, summary)
204 self.handle_edit_error(e, summary)
206 # 'newtimestamp' is not included if no change was made
207 if 'newtimestamp' in result['edit'].keys():
208 self.last_rev_time = client.parse_timestamp(result['edit'].get('newtimestamp'))
209 return result['edit']
211 def handle_edit_error(self, e, summary):
212 if e.code == 'editconflict':
213 raise errors.EditError(self, summary, e.info)
214 elif e.code in ('protectedtitle', 'cantcreate', 'cantcreate-anon', 'noimageredirect-anon',
215 'noimageredirect', 'noedit-anon', 'noedit'):
216 raise errors.ProtectedPageError(self, e.code, e.info)
220 def move(self, new_title, reason='', move_talk=True, no_redirect=False):
221 """Move (rename) page to new_title.
223 If user account is an administrator, specify no_direct as True to not
226 If user does not have permission to move page, an InsufficientPermission
230 if not self.can('move'):
231 raise errors.InsufficientPermission(self)
233 if not self.site.writeapi:
234 raise errors.NoWriteApi(self)
238 data['movetalk'] = '1'
240 data['noredirect'] = '1'
241 result = self.site.api('move', ('from', self.name), to=new_title,
242 token=self.get_token('move'), reason=reason, **data)
243 return result['move']
245 def delete(self, reason='', watch=False, unwatch=False, oldimage=False):
248 If user does not have permission to delete page, an InsufficientPermission
252 if not self.can('delete'):
253 raise errors.InsufficientPermission(self)
255 if not self.site.writeapi:
256 raise errors.NoWriteApi(self)
262 data['unwatch'] = '1'
264 data['oldimage'] = oldimage
265 result = self.site.api('delete', title=self.name,
266 token=self.get_token('delete'),
267 reason=reason, **data)
268 return result['delete']
271 """Purge server-side cache of page. This will re-render templates and other
275 self.site.raw_index('purge', title=self.name)
277 # def watch: requires 1.14
280 def backlinks(self, namespace=None, filterredir='all', redirect=False, limit=None, generator=True):
281 prefix = listing.List.get_prefix('bl', generator)
282 kwargs = dict(listing.List.generate_kwargs(prefix,
283 namespace=namespace, filterredir=filterredir))
285 kwargs['%sredirect' % prefix] = '1'
286 kwargs[prefix + 'title'] = self.name
288 return listing.List.get_list(generator)(self.site, 'backlinks', 'bl', limit=limit, return_values='title', **kwargs)
290 def categories(self, generator=True):
292 return listing.PagePropertyGenerator(self, 'categories', 'cl')
294 # TODO: return sortkey if wanted
295 return listing.PageProperty(self, 'categories', 'cl', return_values='title')
297 def embeddedin(self, namespace=None, filterredir='all', redirect=False, limit=None, generator=True):
298 prefix = listing.List.get_prefix('ei', generator)
299 kwargs = dict(listing.List.generate_kwargs(prefix,
300 namespace=namespace, filterredir=filterredir))
302 kwargs['%sredirect' % prefix] = '1'
303 kwargs[prefix + 'title'] = self.name
305 return listing.List.get_list(generator)(self.site, 'embeddedin', 'ei', limit=limit, return_values='title', **kwargs)
308 return listing.PageProperty(self, 'extlinks', 'el', return_values='*')
310 def images(self, generator=True):
312 return listing.PagePropertyGenerator(self, 'images', '')
314 return listing.PageProperty(self, 'images', '', return_values='title')
317 return listing.PageProperty(self, 'iwlinks', 'iw', return_values=('prefix', '*'))
319 def langlinks(self, **kwargs):
320 return listing.PageProperty(self, 'langlinks', 'll', return_values=('lang', '*'), **kwargs)
322 def links(self, namespace=None, generator=True, redirects=False):
323 kwargs = dict(listing.List.generate_kwargs('pl', namespace=namespace))
325 kwargs['redirects'] = '1'
327 return listing.PagePropertyGenerator(self, 'links', 'pl', **kwargs)
329 return listing.PageProperty(self, 'links', 'pl', return_values='title', **kwargs)
331 def revisions(self, startid=None, endid=None, start=None, end=None,
332 dir='older', user=None, excludeuser=None, limit=50,
333 prop='ids|timestamp|flags|comment|user', expandtemplates=False, section=None):
334 kwargs = dict(listing.List.generate_kwargs('rv', startid=startid, endid=endid,
335 start=start, end=end, user=user, excludeuser=excludeuser))
336 kwargs['rvdir'] = dir
337 kwargs['rvprop'] = prop
339 kwargs['rvexpandtemplates'] = '1'
340 if section is not None:
341 kwargs['rvsection'] = section
343 return listing.RevisionsIterator(self, 'revisions', 'rv', limit=limit, **kwargs)
345 def templates(self, namespace=None, generator=True):
346 kwargs = dict(listing.List.generate_kwargs('tl', namespace=namespace))
348 return listing.PagePropertyGenerator(self, 'templates', 'tl')
350 return listing.PageProperty(self, 'templates', 'tl', return_values='title')
355 def __init__(self, site, name, info=None):
356 Page.__init__(self, site, name, info,
357 extra_properties={'imageinfo':
358 (('iiprop', 'timestamp|user|comment|url|size|sha1|metadata|archivename'), )
360 self.imagerepository = self._info.get('imagerepository', '')
361 self.imageinfo = self._info.get('imageinfo', ({}, ))[0]
363 def imagehistory(self):
364 return listing.PageProperty(self, 'imageinfo', 'ii',
365 iiprop='timestamp|user|comment|url|size|sha1|metadata|archivename')
367 def imageusage(self, namespace=None, filterredir='all', redirect=False,
368 limit=None, generator=True):
369 prefix = listing.List.get_prefix('iu', generator)
370 kwargs = dict(listing.List.generate_kwargs(prefix, title=self.name,
371 namespace=namespace, filterredir=filterredir))
373 kwargs['%sredirect' % prefix] = '1'
374 return listing.List.get_list(generator)(self.site, 'imageusage', 'iu',
375 limit=limit, return_values='title', **kwargs)
377 def duplicatefiles(self, limit=None):
378 return listing.PageProperty(self, 'duplicatefiles', 'df',
382 url = self.imageinfo['url']
383 if not url.startswith('http://'):
384 url = 'http://' + self.site.host + url
385 url = urlparse.urlparse(url)
387 return self.site.connection.get(url[1], url[2])
390 return "<Image object '%s' for %s>" % (self.name.encode('utf-8'), self.site)