Log in with external script fails

I’m writing a simple Python script to reach out to a remote Tryton using the default jsonrpclib. I’m able to reach Tryton and execute the login, but the script fails on the result.

TypeError: Response is not a dict.

Indeed the response is an array with two elements: [<user_id>, <session_id>]. However, the JSON-RPC spec defines a response object https://www.jsonrpc.org/specification which says that there should be a JSON object with a member called result.

Is there a reason why Tryton doesn’t follow the JSON-RPC spec for the login?

I guess you do not set the Content-Type header to ‘application/json’ so for trytond it is not a json-rpc request.

Don’t you mind to share that script??

I have in the header Content-Type: 'application/json-rpc', modified it to Content-Type: 'application/json' but same result. So I digged through the request flow to see where something did go wrong. And finally found it!
In trytond: 6aa537cd2ea7 trytond/protocols/jsonrpc.py there should be an exact match of request keys. However I have an extra key 'jsonrpc' in the set, so the if statement yields wrong and I got the just the result back. I added 'jsonrpc' to the dict and from then on, it worked.
According to the JSON-RPC specification, the member ‘jsonrpc’ tells the server which version of the specification is used.

But now the client fails. Can the if statement be rewritten so that it only checks if id, method and params exists in the parsed_keys and just ignore the rest?

When everything is working, I don’t see any problem to share the script. But I first need to clean it up before I can share it. To much sensitive data and at the moment the whole script is a mess.

1 Like

But it is not in version 1.0 which is the one implemented.

No the goal is to be sure it is a JSON-RPC request. So it must match exactly. Once version 2.0 is supported we should update the test to allow optional ‘jsonrpc’ key.

Ah, ok, didn’t know that. I added the version=1.0 to the ServerProxy and now it works out-of-the-box.

BTW, when I want to share the script. What’s the best category to put it in?

I would put it here continuing the thread where it appeared…

I would rather see a “snippets” category where people can put their small solutions to different problems. Not everybody fully understands what Tryton is capable of.
But for now, Here is a very very basic script in Python3 which can connect to a Tryton server version 4.8, 5.0 or 5.2 (tested with the demo server)

WARNING! Use at your own risk.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

from jsonrpclib import Server as ServerProxy
import jsonrpclib
import json
import base64

# Tryton server connection settings
HOST = 'http://demo4.8.tryton.org'
PORT = '8000'
DB = 'demo4.8'
USER = 'admin'
PASSWORD = 'admin'

class Tryton(object):

    def __init__(self, url):
        self.server = ServerProxy(url, verbose=0, version=1.0, history=None)
        try:
            result = self.server.common.db.login(
                USER,
                {'password': PASSWORD}, 'en_US')

            session = ':'.join(map(str, [USER] + result))
            auth = base64.b64encode(session.encode())
            self.headers = {'AUTHORIZATION' : 'Session ' + auth.decode('utf-8'),
                            'CONNECTION' : 'keep-alive'
                            }           
            with self.server._additional_headers(self.headers) as loggedinClient:
                self.pref = loggedinClient.model.res.user.get_preferences(True,{})
            print('Connection to Tryton succesfull')
        except IOError as msg:
            print('Connection to Tryton failed! %s', msg)
            raise
            
    def execute(self, method, *args):
        args += (self.pref,)
        try:
            with self.server._additional_headers(self.headers) as loggedinClient:
                return getattr(loggedinClient, method)(*args)
        except TypeError:
            a = json.loads(jsonrpclib.history.response)
            print('Execution failed! %s: %s', a['error'][0], a['error'][1])
            raise TypeError('%s: %s' % (a['error'][0], a['error'][1]))

    def logout(self):
        with self.server._additional_headers(self.headers) as loggedinClient:
            loggedinClient.common.db.logout()

if __name__ == "__main__":
    print('Connect to Tryton')
    # connect to tryton and login
    tryton = Tryton("%s:%s/%s/" % (HOST, PORT, DB))

    # get some data from Tryton. Check the documentation on 
    # http://docs.tryton.org/projects/server/en/latest/ref/models/models.html
    # how to get or send data to Tryton
    for sale_order in tryton.execute('model.sale.sale.search_read', 
                [], 0, 50, None, ['reference', 'number', 'party', 'lines']):
        print(sale_order)

    print('Log out from Tryton')
    tryton.logout()

When working with dates and datetimes, you’re out of luck here. The Tryton JsonRPC encoder and decorder have to be used.

1 Like

Hi,

As this is in python, I’m assuming there is a good reason why you are not using proteus for this?

This probably won’t be as fast (until Issue 7783: Allow to use session key instead of login/password - Tryton issue tracker), it uses xmlrpc instead of json-rpc, and is for 5.2 (but should work for 4.8):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from proteus import Model, config

# Tryton server connection settings
HOST = 'demo5.2.tryton.org'
PORT = '8000'
DB = 'demo5.2'
USER = 'admin'
PASSWORD = 'admin'


if __name__ == '__main__':
    config.set_xmlrpc(
        url='http://{user}:{password}@{host}:{port}/{db}/'.format(
            user=USER, password=PASSWORD, host=HOST, port=PORT, db=DB))

    Sale = Model.get('sale.sale')

    domain = [('state', '=', 'processing')]
    max_num_of_records = 2
    sales = Sale.find(domain, limit=max_num_of_records)
    for sale in sales:
        print("{number}: {date} {party} ({lines}) {currency}{value}".format(
            number=sale.number, date=sale.sale_date, party=sale.party.rec_name,
            lines=len(sale.lines), currency=sale.currency.symbol,
            value=sale.total_amount))

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.