tinyerp / odooly

Python library and CLI to interact with Odoo and OpenERP.
https://odooly.rtfd.io/
Other
60 stars 33 forks source link

Error decoding recordset returned by method #2

Closed PCatinean closed 5 years ago

PCatinean commented 5 years ago

I hope this is not an error from my side but I installed odooly in a python virtualenvironment (version 2.7) using pip.

I have a remote method that when executed it creates a project and returns the recordset.

This is what odooly throws when that value is sent:

  File "/opt/docker/simply-solar/odoo/custom/src/simply-solar/venv/local/lib/python2.7/site-packages/odooly.py", line 1800, in wrapper
    res = self._execute(attr, [self.id], *params, **kwargs)
  File "/opt/docker/simply-solar/odoo/custom/src/simply-solar/venv/local/lib/python2.7/site-packages/odooly.py", line 706, in execute
    res = self._execute_kw(obj, method, params, *kw)
  File "/opt/docker/simply-solar/odoo/custom/src/simply-solar/venv/local/lib/python2.7/site-packages/odooly.py", line 445, in <lambda>
    wrapper = lambda s, *args: s._dispatch(name, args)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1602, in __request
    verbose=self.__verbose
  File "/usr/lib/python2.7/xmlrpclib.py", line 1283, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1316, in single_request
    return self.parse_response(response)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1493, in parse_response
    return u.close()
  File "/usr/lib/python2.7/xmlrpclib.py", line 800, in close
    raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault cannot marshal <class 'collections.defaultdict'> objects: 'Traceback (most recent call last):\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 509, in __dump\n    f = self.dispatch[type(value)]\nKeyError: <class \'collections.defaultdict\'>\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File "/opt/odoo/custom/src/odoo/odoo/service/wsgi_server.py", line 125, in wsgi_xmlrpc\n    response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 951, in dumps\n    data = m.dumps(params)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 501, in dumps\n    dump(v, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 523, in __dump\n    f(self, value, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 614, in dump_instance\n    self.dump_struct(value.__dict__, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 594, in dump_struct\n    dump(v, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 523, in __dump\n    f(self, value, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 614, in dump_instance\n    self.dump_struct(value.__dict__, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 594, in dump_struct\n    dump(v, write)\n  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 513, in __dump\n    raise TypeError("cannot marshal %s objects" % type(value))\nTypeError: cannot marshal <class \'collections.defaultdict\'> objects\n'>

Many thanks for opensourcing this great lib, best way to execute remote rpc for Odoo by far.

florentx commented 5 years ago

Hello @PCatinean . When I print the source traceback, I get this:

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 509, in __dump
    f = self.dispatch[type(value)]
KeyError: <class 'collections.defaultdict'>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/odoo/custom/src/odoo/odoo/service/wsgi_server.py", line 125, in wsgi_xmlrpc
    response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 951, in dumps
    data = m.dumps(params)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 501, in dumps
    dump(v, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 523, in __dump
    f(self, value, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 614, in dump_instance
    self.dump_struct(value.__dict__, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 594, in dump_struct
    dump(v, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 523, in __dump
    f(self, value, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 614, in dump_instance
    self.dump_struct(value.__dict__, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 594, in dump_struct
    dump(v, write)
  File "/usr/local/lib/python3.5/xmlrpc/client.py", line 513, in __dump
    raise TypeError("cannot marshal %s objects" % type(value))
TypeError: cannot marshal <class 'collections.defaultdict'> objects

It complains about the RPC method returning a collections.defaultdict. The issue is because XMLRPC (and JSONRPC too, I guess) cannot transfer this type of objects. The issue is probably in you custom module (I don't use defaultdict internally in Odooly). You have to cast the result (or its subelement) to a dict(...) when you return it.

PCatinean commented 5 years ago

Ah so basically when a method returns a record/recordset the lib does not parse it. I assume it would get the id from the returned value and browse it so generate a local representation of the object.

I assume I would need to make a duplicate method or add an argument to change the return type from record/recordset to dictionary then?

florentx commented 5 years ago

Actually it's documented on odoo website: "methods expressed purely in the new API are not available over RPC" https://www.odoo.com/documentation/master/reference/orm.html#compatibility-between-new-api-and-old-api

So you need to decorate the method with @api.multi or @api.model if you want to expose it to RPC.

florentx commented 5 years ago

I'm reading more about it. Actually if your method returns a Recordset, and you want to use it with RPC, you need to decorate it twice : @api.multi or @api.model and @api.returns('self').

    @api.multi
    @api.returns('self')
    def refund(self, date_invoice=None, date=None, description=None, journal_id=None):
        new_invoices = self.browse()
        for invoice in self:
            # create the new invoice
            values = self._prepare_refund(invoice, ...)
            refund_invoice = self.create(values)
            message = _("...")
            refund_invoice.message_post(body=message)
            new_invoices += refund_invoice
        return new_invoices
PCatinean commented 5 years ago

Indeed, as always right you are @florentx. In order to process these values via RPC the returns decorator is needed.

Didn't pay much attention to it before but now I certainly will. Thanks a lot for giving a helping hand on this, I appreciate it tremendously.