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

Reply via email to