Sending JSON command from Python
#1
This might be helpful to someone but here is a small function to send a JSON command to XBMC and parse the response ::

Code:
import json
import httplib
import base64

def send_json_command(method, params=None, id=1, username='', password=''):
    command = {'jsonrpc': '2.0', 'method': method, 'id': id}
        
    if params is not None:
        command['params'] = params
        
    payload = json.dumps(command, ensure_ascii=False)
    payload.encode('utf-8')
        
    headers = {'Content-Type': 'application/json-rpc; charset=utf-8'}

    if self.password != '':
        userpass = base64.encodestring('%s:%s' % (username, password))[:-1]
        headers['Authorization'] = 'Basic %s' % userpass
        
    conn = httplib.HTTPConnection(xbmc_host, xbmc_port)
    conn.request('POST', '/jsonrpc', payload, headers)

    response = conn.getresponse()
    data = None
    if response.status == 200:
        data = response.read()
            
    conn.close()
        
    if data is not None:
        return json.loads(data)
    else:
        return None

just change the xbmc_host and xbmc_port values or pass them as parameters.
Reply
#2
There is an executeJSONRPC( ) method exposed to python Smile (obviously only useful for localhost stuff)
If you have problems please read this before posting

Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.

Image

"Well Im gonna download the code and look at it a bit but I'm certainly not a really good C/C++ programer but I'd help as much as I can, I mostly write in C#."
Reply
#3
I cant remember where I found this python module. I believe it was on this forum. It makes accessing xbmc via json more pythonic and a dream. If anyone knows who wrote this I'd like to attribute them in the file!


xbmc_jsonrpc.py
Code:
import urllib, simplejson

class JsonRPCError(Exception):
    def __init__(self, code, message):
        Exception.__init__(self, message)
        self.code = code
        self.message = message
        
class ConnectionError(Exception):
    def __init__(self, code, message):
        Exception.__init__(self, message)
        self.code = code
        self.message = message
        
class UserPassError(Exception): pass
        
class Namespace:
        def __init__(self,name,url):
                self.name = name
                self.url = url
                self.__handler_cache = {}

        def __getattr__(self, method):
                if method in self.__handler_cache:
                        return self.__handler_cache[method]

                def handler(**kwargs):
                        postdata = '{"jsonrpc": "2.0","id":"1","method":"%s.%s"' % (self.name,method)
                        if kwargs:
                                postdata += ',"params": {'
                                append = ''
                                for k in kwargs:
                                        if append: append += ','
                                        append += '"%s":"%s"' % (str(k),str(kwargs[k]))
                                postdata += append + '}'
                        postdata += '}'
                        #print postdata
                        try:
                                fobj = urllib.urlopen(self.url,postdata)
                        except IOError,e:
                                if e.args[0] == 'http error':
                                         if e.args[1] == 401: raise UserPassError()
                                raise ConnectionError(e.errno,'Connection error: ' + str(e.errno))

                        try:
                                json = simplejson.loads(fobj.read())
                        except:
                                fobj.close()

                        if 'error' in json: raise JsonRPCError(json['error']['code'],json['error']['message'])

                        return json['result']


                handler.method = method
                self.__handler_cache[method] = handler
                return handler

class jsonrpcAPI:
        def __init__(self,url='http://127.0.0.1:8080/jsonrpc',user=None,password=None):
                if password: url = url.replace('http://','http://%s:%s@' % (user,password))
                #print "URL: " + url
                self.url = url
                self.__namespace_cache = {}

        def __getattr__(self, namespace):
                if namespace in self.__namespace_cache:
                        return self.__namespace_cache[namespace]

                nsobj = Namespace(namespace,self.url)
                self.__namespace_cache[namespace] = nsobj
                return nsobj

Some test code I wrote to figure it out:
Code:
def jp(jsondata):
    try:
        dirs = jsondata['directories']
    except KeyError:
        pass
    else:
        for result in dirs:
            print result['fanart'], result['file'], result['label'], result['thumbnail']
            
    try:
        files = jsondata['files']
    except KeyError:
        pass
    else:
        for result in files:
            print result['fanart'], result['file'], result['label'], result['thumbnail']
            
            
    #print simplejson.dumps(jsondata, sort_keys=True, indent=4)
    print "BREAK"

_jrapi = jsonrpc.jsonrpcAPI( user=http_user,password=http_pass )
print jp(_jrapi.JSONRPC.Introspect())
print jp(_jrapi.Files.GetSources( media='video' ))
print jp(_jrapi.Files.GetDirectory( media='video', directory='addons://sources/video' ))
print jp(_jrapi.Files.GetDirectory( media='video', directory='plugin://plugin.video.twit/' ))
print jp(_jrapi.Files.GetDirectory( media='video', directory='plugin://plugin.video.twit/?url=http%3A%2F%2Ffeeds.twit.tv%2Ftwit_video_large&mode=1&name=This+WEEK+in+TECH' ))
Reply
#4
Actually have thought about including such a wrap by default around the executeJSONRPC part, it makes sense to have
If you have problems please read this before posting

Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.

Image

"Well Im gonna download the code and look at it a bit but I'm certainly not a really good C/C++ programer but I'd help as much as I can, I mostly write in C#."
Reply
#5
topfs2 Wrote:Actually have thought about including such a wrap by default around the executeJSONRPC part, it makes sense to have

That would be a great idea especially for new developers. It makes "wrap"ing your mind around this jsonrpc stuff easier.
Reply
#6
daledude Wrote:I cant remember where I found this python module. I believe it was on this forum. It makes accessing xbmc via json more pythonic and a dream. If anyone knows who wrote this I'd like to attribute them in the file!

Code:
...

Thanks for that, I've adapted it slightly to use httplib and exposing a single JSONConnection class.

Code:
try:
    import simplejson as json
except ImportError:
    import json
    
import base64
import httplib

__all__ = ['JSONConnection', 'JSONRPCError', 'JSONConnectionError', 'JSONUserPassError']

class JSONError(Exception):
    pass


class JSONRPCError(JSONError):
    def __init__(self, code, message):
        JSONError.__init__(self, message)
        self.code = code
        self.message = message


class JSONConnectionError(JSONError):
    def __init__(self, code, message):
        JSONError.__init__(self, message)
        self.code = code
        self.message = message


class JSONUserPassError(JSONError):
    pass


class JSONConnection(object):
    def __init__(self, host='127.0.0.1', port=8080, username='xbmc', password=''):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self._namespace_cache = {}

    def __getattr__(self, namespace):
        if namespace in self._namespace_cache:
            return self._namespace_cache[namespace]

        nsobj = self.Namespace(namespace, self)
        self._namespace_cache[namespace] = nsobj
        
        return nsobj
    
    class Namespace(object):
        def __init__(self, name, connection):
            self.name = name
            self.connection = connection
            self._id = 1
            self._handler_cache = {}
    
        def __getattr__(self, method):
            if method in self._handler_cache:
                return self._handler_cache[method]
    
            def handler(**kwargs):
                data = {'jsonrpc': '2.0',
                    'id': self._id,
                    'method': '%s.%s' % (self.name, method)
                }
                
                if args:
                    if len(args) == 1:
                        data['params'] = args[0]
                    else:
                        data['params'] = []
                        for arg in args:
                            data['params'].append(arg)
                elif kwargs:
                    data['params'] = {}
                    
                    for k in kwargs:
                        data['params'][k] = kwargs[k]
                        
                postdata = json.dumps(data, ensure_ascii=False)
                postdata = postdata.encode('utf-8')
                
                headers = {'Content-Type': 'application/json-rpc; charset=utf-8'}
        
                if self.connection.password != '':
                    userpass = base64.encodestring('%s:%s' % \
                        (self.connection.username, self.connection.password))[:-1]
                    headers['Authorization'] = 'Basic %s' % userpass
                
                conn = httplib.HTTPConnection(self.connection.host,
                    self.connection.port)
                conn.request('POST', '/jsonrpc', postdata, headers)
        
                data = None
                response = conn.getresponse()
                
                if response.status == httplib.OK:
                    data = response.read()
        
                conn.close()
                
                if response.status != httplib.OK:
                    if response.status == httplib.UNAUTHORIZED:
                        raise JSONUserPassError()
                    else:
                        raise JSONConnectionError(response.status,
                            'Connection Error: %s' % httplib.responses[response.status])
                    
                conn.close()
                
                self._id += 1
                
                if data is not None:
                    response = json.loads(data)
                    if 'error' in response:
                        raise JSONRPCError(json['error']['code'],
                            json['error']['message'])
                        
                    return response['result']
                else:
                    return None
                
            handler.method = method
            self._handler_cache[method] = handler
            
            return handler


if __name__ == '__main__':
    conn = JSONConnection()
    data = conn.JSONRPC.Introspect()
    print(json.dumps(data, sort_keys=True, indent=4))

Edit: Added code to send positional args. XBMC expects a single position arg to be sent as is which may not be valid JSON-RPC 2.0
Reply
#7
Should it be "def handler(*args, **kwargs):" ??

Else this line produces an error "if args:"

Also, to work with new API changes, you need to change:
headers = {'Content-Type': 'application/json-rpc; charset=utf-8'}
to:
headers = {'Content-Type': 'application/json; charset=utf-8'}
Reply

Logout Mark Read Team Forum Stats Members Help
Sending JSON command from Python0