Преглед на файлове

Some quick updates to wikitools before I commit Page.

* Site: api_query() -> _api_query(); api_query() acts as a wrapper for _api_query(), accepting API params as **kwargs.
* Various cleanup throughout and minor fixes.
tags/v0.1^2
Ben Kurtovic преди 13 години
родител
ревизия
4bada57a9b
променени са 4 файла, в които са добавени 85 реда и са изтрити 69 реда
  1. +1
    -1
      wiki/tools/category.py
  2. +10
    -1
      wiki/tools/exceptions.py
  3. +69
    -62
      wiki/tools/site.py
  4. +5
    -5
      wiki/tools/user.py

+ 1
- 1
wiki/tools/category.py Целия файл

@@ -13,6 +13,6 @@ class Category(Page):
""" """
params = {"action": "query", "list": "categorymembers", params = {"action": "query", "list": "categorymembers",
"cmlimit": limit, "cmtitle": self.title} "cmlimit": limit, "cmtitle": self.title}
result = self.site.api_query(params)
result = self._site._api_query(params)
members = result['query']['categorymembers'] members = result['query']['categorymembers']
return [member["title"] for member in members] return [member["title"] for member in members]

+ 10
- 1
wiki/tools/exceptions.py Целия файл

@@ -30,5 +30,14 @@ class PermissionsError(WikiToolsetError):
class NamespaceNotFoundError(WikiToolsetError): class NamespaceNotFoundError(WikiToolsetError):
"""A requested namespace name or namespace ID does not exist.""" """A requested namespace name or namespace ID does not exist."""


class PageNotFoundError(WikiToolsetError):
"""Attempting to get certain information about a page that does not
exist."""

class InvalidPageError(WikiToolsetError):
"""Attempting to get certain information about a page whose title is
invalid."""

class UserNotFoundError(WikiToolsetError): class UserNotFoundError(WikiToolsetError):
"""Attempting to get information about a user that does not exist."""
"""Attempting to get certain information about a user that does not
exist."""

+ 69
- 62
wiki/tools/site.py Целия файл

@@ -75,6 +75,55 @@ class Site(object):
if logged_in_as is None or name != logged_in_as: if logged_in_as is None or name != logged_in_as:
self._login(login) self._login(login)


def _api_query(self, params):
"""Do an API query with `params` as a dict of parameters.

This will first attempt to construct an API url from self._base_url and
self._script_path. We need both of these, or else we'll raise
SiteAPIError.

We'll encode the given params, adding format=json along the way, and
make the request through self._opener, which has built-in cookie
support via self._cookiejar, a User-Agent
(wiki.tools.constants.USER_AGENT), and Accept-Encoding set to "gzip".
Assuming everything went well, we'll gunzip the data (if compressed),
load it as a JSON object, and return it.

If our request failed, we'll raise SiteAPIError with details.

There's helpful MediaWiki API documentation at
<http://www.mediawiki.org/wiki/API>.
"""
if self._base_url is None or self._script_path is None:
e = "Tried to do an API query, but no API URL is known."
raise SiteAPIError(e)

url = ''.join((self._base_url, self._script_path, "/api.php"))
params["format"] = "json" # this is the only format we understand
data = urlencode(params)

print url, data # debug code

try:
response = self._opener.open(url, data)
except URLError as error:
if hasattr(error, "reason"):
e = "API query at {0} failed because {1}."
e = e.format(error.geturl, error.reason)
elif hasattr(error, "code"):
e = "API query at {0} failed; got an error code of {1}."
e = e.format(error.geturl, error.code)
else:
e = "API query failed."
raise SiteAPIError(e)
else:
result = response.read()
if response.headers.get("Content-Encoding") == "gzip":
stream = StringIO(result)
gzipper = GzipFile(fileobj=stream)
result = gzipper.read()
return loads(result) # parse as a JSON object

def _load_attributes(self, force=False): def _load_attributes(self, force=False):
"""Load data about our Site from the API. """Load data about our Site from the API.


@@ -94,13 +143,13 @@ class Site(object):


if self._namespaces is None or force: if self._namespaces is None or force:
params["siprop"] = "general|namespaces|namespacealiases" params["siprop"] = "general|namespaces|namespacealiases"
result = self.api_query(params)
result = self._api_query(params)
self._load_namespaces(result) self._load_namespaces(result)
elif all(attrs): # everything is already specified and we're not told elif all(attrs): # everything is already specified and we're not told
return # to force a reload, so do nothing return # to force a reload, so do nothing
else: # we're only loading attributes other than _namespaces else: # we're only loading attributes other than _namespaces
params["siprop"] = "general" params["siprop"] = "general"
result = self.api_query(params)
result = self._api_query(params)


res = result["query"]["general"] res = result["query"]["general"]
self._name = res["wikiid"] self._name = res["wikiid"]
@@ -197,7 +246,7 @@ class Site(object):
we are logged out. we are logged out.
""" """
params = {"action": "query", "meta": "userinfo"} params = {"action": "query", "meta": "userinfo"}
result = self.api_query(params)
result = self._api_query(params)
return result["query"]["userinfo"]["name"] return result["query"]["userinfo"]["name"]


def _get_username(self): def _get_username(self):
@@ -253,7 +302,7 @@ class Site(object):
params = {"action": "login", "lgname": name, "lgpassword": password} params = {"action": "login", "lgname": name, "lgpassword": password}
if token is not None: if token is not None:
params["lgtoken"] = token params["lgtoken"] = token
result = self.api_query(params)
result = self._api_query(params)
res = result["login"]["result"] res = result["login"]["result"]


if res == "Success": if res == "Success":
@@ -282,58 +331,16 @@ class Site(object):
save it, if it supports that sort of thing. save it, if it supports that sort of thing.
""" """
params = {"action": "logout"} params = {"action": "logout"}
self.api_query(params)
self._api_query(params)
self._cookiejar.clear() self._cookiejar.clear()
self._save_cookiejar() self._save_cookiejar()


def api_query(self, params):
"""Do an API query with `params` as a dict of parameters.

This will first attempt to construct an API url from self._base_url and
self._script_path. We need both of these, or else we'll raise
SiteAPIError.
def api_query(self, **kwargs):
"""Do an API query with `kwargs` as the parameters.


We'll encode the given params, adding format=json along the way, and
make the request through self._opener, which has built-in cookie
support via self._cookiejar, a User-Agent
(wiki.tools.constants.USER_AGENT), and Accept-Encoding set to "gzip".
Assuming everything went well, we'll gunzip the data (if compressed),
load it as a JSON object, and return it.

If our request failed, we'll raise SiteAPIError with details.

There's helpful MediaWiki API documentation at
<http://www.mediawiki.org/wiki/API>.
See _api_query()'s documentation for details.
""" """
if self._base_url is None or self._script_path is None:
e = "Tried to do an API query, but no API URL is known."
raise SiteAPIError(e)

url = ''.join((self._base_url, self._script_path, "/api.php"))
params["format"] = "json" # this is the only format we understand
data = urlencode(params)

print url, data # debug code

try:
response = self._opener.open(url, data)
except URLError as error:
if hasattr(error, "reason"):
e = "API query at {0} failed because {1}."
e = e.format(error.geturl, error.reason)
elif hasattr(error, "code"):
e = "API query at {0} failed; got an error code of {1}."
e = e.format(error.geturl, error.code)
else:
e = "API query failed."
raise SiteAPIError(e)
else:
result = response.read()
if response.headers.get("Content-Encoding") == "gzip":
stream = StringIO(result)
gzipper = GzipFile(fileobj=stream)
result = gzipper.read()
return loads(result) # parse as a JSON object
return self._api_query(kwargs)


def name(self): def name(self):
"""Returns the Site's name (or "wikiid" in the API), like "enwiki".""" """Returns the Site's name (or "wikiid" in the API), like "enwiki"."""
@@ -389,32 +396,32 @@ class Site(object):
e = "There is no namespace with name '{0}'.".format(name) e = "There is no namespace with name '{0}'.".format(name)
raise NamespaceNotFoundError(e) raise NamespaceNotFoundError(e)


def get_page(self, pagename):
"""Returns a Page object for the given pagename.
def get_page(self, title, follow_redirects=False):
"""Returns a Page object for the given title (pagename).


Will return a Category object instead if the given pagename is in the
Will return a Category object instead if the given title is in the
category namespace. As Category is a subclass of Page, this should not category namespace. As Category is a subclass of Page, this should not
cause problems. cause problems.


Note that this doesn't do any checks for existence or
Note that this doesn't do any direct checks for existence or
redirect-following - Page's methods provide that. redirect-following - Page's methods provide that.
""" """
prefixes = self.namespace_id_to_name(NS_CATEGORY, all=True) prefixes = self.namespace_id_to_name(NS_CATEGORY, all=True)
prefix = pagename.split(":", 1)[0]
if prefix != pagename: # avoid a page that is simply "Category"
prefix = title.split(":", 1)[0]
if prefix != title: # avoid a page that is simply "Category"
if prefix in prefixes: if prefix in prefixes:
return Category(self, pagename)
return Page(self, pagename)
return Category(self, title, follow_redirects)
return Page(self, title, follow_redirects)


def get_category(self, catname):
def get_category(self, catname, follow_redirects=False):
"""Returns a Category object for the given category name. """Returns a 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 get_page("Category:" + catname). really just shorthand for get_page("Category:" + catname).
""" """
prefix = self.namespace_id_to_name(NS_CATEGORY) prefix = self.namespace_id_to_name(NS_CATEGORY)
pagename = "{0}:{1}".format(prefix, catname)
return Category(self, pagename)
pagename = ':'.join((prefix, catname))
return Category(self, pagename, follow_redirects)


def get_user(self, username=None): def get_user(self, username=None):
"""Returns a User object for the given username. """Returns a User object for the given username.


+ 5
- 5
wiki/tools/user.py Целия файл

@@ -69,8 +69,8 @@ class User(object):
is not defined. This defines it. is not defined. This defines it.
""" """
params = {"action": "query", "list": "users", "ususers": self._name, params = {"action": "query", "list": "users", "ususers": self._name,
"usprop": "blockinfo|groups|rights|editcount|registration|emailable|gender"}
result = self._site.api_query(params)
"usprop": "blockinfo|groups|rights|editcount|registration|emailable|gender"}
result = self._site._api_query(params)
res = result["query"]["users"][0] res = result["query"]["users"][0]


# normalize our username in case it was entered oddly # normalize our username in case it was entered oddly
@@ -130,7 +130,7 @@ class User(object):
Makes an API query if `force` is True or if we haven't made one Makes an API query if `force` is True or if we haven't made one
already. already.
""" """
if self._exists is None or force:
if not hasattr(self, "_exists") or force:
self._load_attributes() self._load_attributes()
return self._exists return self._exists


@@ -212,7 +212,7 @@ class User(object):
conventions are followed. conventions are followed.
""" """
prefix = self._site.namespace_id_to_name(NS_USER) prefix = self._site.namespace_id_to_name(NS_USER)
pagename = ''.join((prefix, ":", self._name))
pagename = ':'.join((prefix, self._name))
return Page(self._site, pagename) return Page(self._site, pagename)


def get_talkpage(self): def get_talkpage(self):
@@ -222,5 +222,5 @@ class User(object):
conventions are followed. conventions are followed.
""" """
prefix = self._site.namespace_id_to_name(NS_USER_TALK) prefix = self._site.namespace_id_to_name(NS_USER_TALK)
pagename = ''.join((prefix, ":", self._name))
pagename = ':'.join((prefix, self._name))
return Page(self._site, pagename) return Page(self._site, pagename)

Зареждане…
Отказ
Запис