Yurik has uploaded a new change for review. https://gerrit.wikimedia.org/r/68943
Change subject: API python framework ...................................................................... API python framework Change-Id: I87f2c2fa27357a57354b235f26c2350c38e37307 --- A maintenance/api.py 1 file changed, 177 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ZeroRatedMobileAccess refs/changes/43/68943/1 diff --git a/maintenance/api.py b/maintenance/api.py new file mode 100644 index 0000000..ef78147 --- /dev/null +++ b/maintenance/api.py @@ -0,0 +1,177 @@ +from __future__ import print_function +import json +import requests +try: + import urllib.parse as urlparse +except ImportError: + import urlparse + + +class ConsoleLog(object): + """ + Basic console logger. Most frameworks would probably want to implement their own. + """ + def __init__(self, verbosity=5): + self.verbosity = verbosity + + def log(self, level, msg): + """If level is less than or equal to verbosity, prints level and the msg""" + if self.isEnabled(level): + print(str(level) + ': ' + str(msg)) + + def isEnabled(self, level): + """True if level is less than or equal to verbosity set for this instance""" + return level <= self.verbosity + + +class ApiError(Exception): + """ + Any error reported by the API is included in this exception + """ + def __init__(self, data): + self.data = data + + def __str__(self): + return json.dumps(self.data) + +class Site(object): + """ + Public properties (member variables at the moment): + * url: Full url to site's api.php + * session: current request.session object + * log: an object that will be used for logging. ConsoleLog is created by default + """ + + def __init__(self, url, session=None, log=None): + self.session = session if session is not None else requests.session() + self.log = log if log is not None else ConsoleLog() + self.url = url + + def __call__(self, action, **kwargs): + """ + Make an API call with any arguments provided as named values: + + data = site('query', meta='siteinfo') + + By default uses GET request to the default URL set in the Site constructor. + In case of an error, ApiError exception will be raised + + Several special "magic" parameters could be used to customize api call. + Special parameters must be all CAPS to avoid collisions with the server API: + :param POST: Use POST method when calling server API. Value is ignored. + :param HTTPS: Force https (ssl) protocol for this request. Value is ignored. + :param HEADERS: Additional headers (dict) to include with request. Value is ignored. + """ + # Magic CAPS parameters + usePost = 'POST' in kwargs or action in ['login', 'edit'] + forceSSL = action == 'login' or 'SSL' in kwargs or 'HTTPS' in kwargs + headers = None if 'HEADERS' not in kwargs else kwargs['HEADERS'] + + # Clean up magic CAPS params as they shouldn't be passed to the server + for k in ['POST', 'SSL', 'HTTPS', 'HEADERS']: + if k in kwargs: + del kwargs[k] + + # Make server call + kwargs['action'] = action + kwargs['format'] = 'json' + data = self.request(kwargs, headers, usePost, forceSSL).json() + + # Handle success and failure + if 'error' in data: + raise ApiError(data['error']) + if 'warnings' in data: + self.log(2, data['warnings']) + return data + + def login(self, user, password): + res = self('login', lgname=user, lgpassword=password)['login'] + if res['result'] == 'NeedToken': + res = self('login', lgname=user, lgpassword=password, lgtoken=res['token'])['login'] + if res['result'] != 'Success': + raise ApiError(res) + + def query(self, **kwargs): + if 'continue' not in kwargs: + kwargs['continue'] = '' + req = kwargs + while True: + result = self('query', **req) + if 'query' in result: + yield result['query'] + if 'continue' not in result: + break + # re-send all continue values in the next call + req = dict(kwargs) + req.update(result['continue']) + + def __queryPages(self, **kwargs): + """ UNFINISHED!!! """ + incomplete={} + changed={} + for result in self.query(**kwargs): + for page in result['pages']: + id = page['id'] + if id in changed: + continue + if id in incomplete: + p = incomplete[id] + del incomplete[id] + if p['lastrevid'] != page['lastrevid']: + # someone else modified this page, it must be requested anew separately + changed[id] = '' + continue + mergePage(p, page) + else: + p = page + if 'incomplete' in p: + inomplete[id] = p + else: + yield p + if incomplete: + # pages could have been deleted, deal with that + pass + if changed: + # some pages have been changed between api calls, try to get them again + pass + + def __mergePage(a, b): + """ UNFINISHED!!! """ + for k in b: + v = b[k] + if k in a: + if isinstance(v, dict): + mergePage(a[k], v) + elif isinstance(v, list): + a[k] = a[k] + v + else: + a[k] = v + else: + a[k] = v + + def token(self, type='edit'): + return self('tokens', type=type)['tokens'][type+'token'] + + def request(self, params=None, headers=None, usePost=False, forceSSL=False): + """Make either a post or a get low level request to the server""" + url = self.url + if forceSSL: + parts = list(urlparse.urlparse(url)) + parts[0] = 'https' + url = urlparse.urlunparse(parts) + if usePost: + r = self.session.post(url, data=params, headers=headers) + else: + r = self.session.get(url, params=params, headers=headers) + if self.log.isEnabled(5): + dbg = [r.request.url] + if headers is not None: + dbg.append(headers) + self.log.log(5, dbg) + return r + +def wikimedia(language='en', site='wikipedia', scheme='http', session=None, log=None): + """Create a Site object for Wikimedia Foundation site in this format: + [scheme]://[language].[site].org/w/api.php + """ + return Site(scheme + '://' + language + '.' + site + '.org/w/api.php', session, log) -- To view, visit https://gerrit.wikimedia.org/r/68943 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I87f2c2fa27357a57354b235f26c2350c38e37307 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ZeroRatedMobileAccess Gerrit-Branch: master Gerrit-Owner: Yurik <yu...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits