-
Notifications
You must be signed in to change notification settings - Fork 24.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC][POC] new XMLRPC endpoint #3989
base: master
Are you sure you want to change the base?
Conversation
Why written as a module? This can be done in the core. |
Because it's a proof of concept, that way I don't have to deal with conflicts if it sits here for 3 years. |
fc8e28a
to
99c16b6
Compare
219d711
to
857c099
Compare
openerp/modules/registry.py
Outdated
@@ -457,8 +457,7 @@ def check_registry_signaling(cls, db_name): | |||
changed = False | |||
if openerp.multi_process and db_name in cls.registries: | |||
registry = cls.get(db_name) | |||
cr = registry.cursor() | |||
try: | |||
with registry.cursor() as cr: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be with(closing(registry.cursor()))
for identical behaviour, otherwise the transaction will be committed. Shouldn't have any impact though, possibly slightly less traffic (cursors are rollbacked when closed, so a regular with cursor
will commit then rollback I think)
41b5657
to
3f4ed7c
Compare
3f4ed7c
to
e19410a
Compare
e19410a
to
9b45719
Compare
ada463e
to
325ed41
Compare
The current XMLRPC endpoints (`/xmlrpc/` and `/xmlrpc/2/`) use the old calling conventions (CC) of odoo-7. They can't be converted to the new CC of the "new" odoo-8 ORM API as there is not enough information at their level to do so (knowledge of whether/where ids and contexts are). This means new-CC methods must currently be decorated with `@model` to be exposed over RPC, or calling them raises a TypeError (number of parameters does not match). The old CC can't be deprecated/removed until an RPC endpoint using the new CC is available. At the same time, while working on the webservice documentation, it turns out that Odoo's xmlrpc layer is pretty idiosyncratic and doesn't really take advantage of xmlrpc and the facilities provided by xmlrpc libraries. We use the opportunity of defining a new endpoint switch to explore/improvate that part as well. Problems of the existing endpoint / system ========================================== 1. does RPC over RPC (implements its own Odoo RPC over XML-RPC) so can't take advantage of library conveniences when they exist 2. only works with dicts and lists, not recordset or other types of mappings and sequences 3. doesn't allow None/nil/null, will break clients if support is enabled, this is an issue because json-rpc serialises None by default so devs lose the habit of returning a dummy placeholder as it works fine from the web client 4. user a non-standard authentication scheme Calling Conventions (improves on 1 and 2) ========================================= call('admin_function', { 'args': [...], 'kwargs': {...}, }) call('model.method', { 'records': [...], 'context': {...}, 'args': [...], 'kwargs': {...}, }) Each call now uses a single parameter, a dictionary with records, context, args (python positional arguments) and kwargs (python named arguments), all four values are optional. The records and context are extracted by the server to build the environment and recordset, the args and kwargs are used when calling the function. This scheme makes it clear what each argument is for and brings the missing abstraction RPC abstraction needed for the "new" odoo-8 API. Even if it is not strictly necessary, it has been decided to use the same scheme also for admin functions (e.g. database management). Custom XMLRPC serializer (improves on 2) ======================================== New CC methods can return recordsets, which the new RPC endpoint has to automatically convert somehow, the easiest was to serialise recordsets as lists of ids (that's coherent with the old/new API decorators, and means e.g. `search` returns the same thing). Customizing `xmlrpclib.Marshaller` didn't work because it dispatches by strict type equality, so it wasn't possible to hook into it to serialise arbitrary recordsets (they get types created on-the-fly). A new marshaller was reimplemented on top of lxml instead. It provides the following features not provided by `xmlrpclib.Marshaller`: * serialises recordsets to lists of ids, uses `BaseModel.ids` (ignores NewId) * converts all mapping keys to strings (same as `json.dumps`) instead of raising an error (see odoo#12667 and probably others I didn't find) * can serialise arbitrary mappings (e.g. `Counter`, `defaultdict`) not just `dict` * can serialise arbitrary iterables, not just `list` and `tuple` Allow None/nil/null (fixes 3) ============================= Of the 5 libraries we have examples for, 4 either enable the feature by default or via a simple flag. The last one (Apache XML-RPC) can be coerced into supporting it using ~15 lines of code which is downright terse as far as the combination of Java and dynamically typed RPC goes. This allows more natural returns from methods (aka don't return anything instead of the artificial unconditional `return True` and `return False` peppered through the codebase) and is in line with JSON-RPC. Endpoint (avoids conflict with existing) ======================================== Uses `/RPC2`: * does not conflict with existing endpoints * used for the examples in the [XML-RPC specification][1] * a number of documents call it the conventional XML-RPC path * at least [one stdlib defaults to /RPC2][Ruby XMLRPC] when no `path` is provided Authentication (fixes 4, also improves on 1) ============================================ All xmlrpc libraries surveyed provide built-in support for [Basic Authentication](http://en.wikipedia.org/wiki/Basic_access_authentication). The new endpoint thus replaces the custom authentication by BA * no less (or more) secure than the existing scheme: relies on underlying transport being HTTPS for security * avoids an extraneous auth step to resolve a uid (although that means if the "current user"'s uid is needed it has to be resolved separately) * no need to keep uid/password around, they're embedded in the xmlrpc proxy object * easy to integrate (Basic sends "cleartext" username and password, they're just base64-encoded) Leverage XML-RPC conventions (improves on 1) ============================================ XML-RPC `methodName` is conventionally a dotted path (e.g. `system.listMethods`), which most libraries can map onto the language's own dereferencing/attribute access. This maps nicely to Odoo's models: the last segment of the `methodName` is the method itself, the rest is the model name. This means `read` for `account.account` can be called as: a_db.account.account.read(...) Ruby's stdlib XMLRPC::Client has no separation between dereferencing and method call, but it's still possible to proxy to a model then call a number of methods on it e.g. partner = a_db.proxy('res.partner') partner.read(...) partner.write(...) Because authentication depends on the db (or lack thereof), the database name is part of the endpoint URL (query parameter) * Admin methods (e.g. create/drop database) are done on a DB-less endpoint * Method calls on models are calls on DB'd endpoint with at least one dot in methodName Code comparison =============== setup common = xmlrpc.client.ServerProxy('https://{}/xmlrpc/2/common'.format(domain)) uid = common.authenticate(db, username, password, {}) models = xmlrpc.client.ServerProxy('https://{}/xmlrpc/2/object'.format(domain)) partners = lambda *args: models.execute_kw(db, uid, password, 'res.partner', *args) partners('read', ...) becomes db = xmlrpclib.ServerProxy(f'https://{username}:{password}@{domain}/RPC2/{db}') partners = db.res.partner partners.read(...) or in Ruby common = XMLRPC::Client.new(host, "/xmlrpc/2/common", 8069) uid = common.authenticate(db, username, password, {}) models = XMLRPC::Client.new(host, "/xmlrpc/2/object", 8069) partners = lambda {|*args| models.execute_kw(db, uid, password, 'res.partner', *args) } partners.call('read', …) becomes db = XMLRPC::Client.new(host, '/RPC2/' + db, 8069, nil, nil, username, password) partners = db.proxy('res.partner') partners.read(…) simple method call models.execute_kw( db, uid, password, 'res.partner', 'check_access_rights', ['read'], {'raise_exception': False}) becomes partners.check_access_rights({ 'args': ['read'], 'kwargs': {'raise_exception': False} }) search models.execute_kw( db, uid, password, 'res.partner', 'search', [[['is_company', '=', True], ['customer', '=', True]]]) becomes partners.search({'kwargs': { 'domain': [['is_company', '=', True], ['customer', '=', True]] }}) read models.execute_kw( db, uid, password, 'res.partner', 'read', [ids]) becomes partners.read({'records': ids}) Co-authored-by: Julien Castiaux <juc@odoo.com> Closes odoo#3989 [XML-RPC specification]: http://xmlrpc.com/ [Ruby XMLRPC]: http://www.ruby-doc.org/stdlib-2.1.5/libdoc/xmlrpc/rdoc/XMLRPC/Client.html#method-c-new
@xmo-odoo Any news on this ? I heard it should be compatible with Python 1.3. cc @Julien00859 who is that kind of guy who still works with python 1. |
Should be merged in 17.0 once the small conflict with http.py is resolved. #neverstopeveloving |
Well, it's been 9 years and you do have conflicts ¯_(ツ)_/¯ |
It seems your last rebase did not go well, could you do it again ? There are still some conflicts. |
By now this pull request deserves some special medal or award I believe 😄 Waiting for the 10-year party in december! |
This monkeypatching causes issues with the XMLRPC testing, since those run in process after loading the module they use the monkeypatched marshaller and thus perform non-standard calls / marshalling. This patching does not seem at all necessary as as far as I can tell we're only dumping to xmlrpc in this specific location, and so can just use the custom marshaller directly. It's also very weird to monkeypatch from a controller submodule in the depth of base. Add a simplified `dumps` wrapper for convenience, it wraps the dumped content into the default response since that's the only thing we need it for. Avoids copy/pasting the marshalling code in multiple locations. Note that we do need a marshaller instance per dump call because it has some local state (`memo`) in order to detect recursive content, we could probably rewrite the thing with an explicit memo parameter through the stack but it's likely not necessary (and if we did that it should be in odoo#3989 anyway, using a direct-threaded `memo` parameter was mentioned).
This monkeypatching causes issues with the XMLRPC testing, since those run in process after loading the module they use the monkeypatched marshaller and thus perform non-standard calls / marshalling. This patching does not seem at all necessary as as far as I can tell we're only dumping to xmlrpc in this specific location, and so can just use the custom marshaller directly. It's also very weird to monkeypatch from a controller submodule in the depth of base. Add a simplified `dumps` wrapper for convenience, it wraps the dumped content into the default response since that's the only thing we need it for. Avoids copy/pasting the marshalling code in multiple locations. Note that we do need a marshaller instance per dump call because it has some local state (`memo`) in order to detect recursive content, we could probably rewrite the thing with an explicit memo parameter through the stack but it's likely not necessary (and if we did that it should be in #3989 anyway, using a direct-threaded `memo` parameter was mentioned). Part-of: #175104 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
v18 is near ! Is it merged ? |
Only 7 files conflicting, mostly doc. |
@Julien00859 Could you rebase and r+ ? |
This monkeypatching causes issues with the XMLRPC testing, since those run in process after loading the module they use the monkeypatched marshaller and thus perform non-standard calls / marshalling. This patching does not seem at all necessary as as far as I can tell we're only dumping to xmlrpc in this specific location, and so can just use the custom marshaller directly. It's also very weird to monkeypatch from a controller submodule in the depth of base. Add a simplified `dumps` wrapper for convenience, it wraps the dumped content into the default response since that's the only thing we need it for. Avoids copy/pasting the marshalling code in multiple locations. Note that we do need a marshaller instance per dump call because it has some local state (`memo`) in order to detect recursive content, we could probably rewrite the thing with an explicit memo parameter through the stack but it's likely not necessary (and if we did that it should be in odoo#3989 anyway, using a direct-threaded `memo` parameter was mentioned). Part-of: odoo#175104 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
The current XMLRPC endpoints (
/xmlrpc/
and/xmlrpc/2/
) use the old calling conventions (CC). They can't be converted to the new CC as there is not enough information at their level to do so (knowledge of whether/where ids and contexts are). This means new-CC methods must currently be decorated with@model
or@multi
to be exposed over RPC, or calling them raises a TypeError (number of parameters does not match). The old CC can't be deprecated/removed until an RPC endpoint using the new CC is available.At the same time, while working on the webservice doc I found out Odoo's xmlrpc layer is pretty idiosyncratic and doesn't really take advantage of xmlrpc and the facilities provided by xmlrpc libraries. The endpoint switch caused by the new CC is the occasion to explore/improve that part as well.
Problems of the existing endpoint / system
None
by default so devs lose the habit of returning a dummy placeholder as it works fine from the web clientCalling Conventions (improves on 1 and 2)
Splits the ids (if any) and context (if any) from positional and keyword arguments in order to better match the "new API" calling conventions (where these informations are respectively part of the recordset and environment not the method call). In the RPC layer, these two items are part of the
subject
positional argument which can be:records
andcontext
. Therecords
key is browsed (or ignored if missing/empty) and thecontext
key is used to seed the Environment (or ignored if missing)This scheme seems to provide the best flexibility and terseness, as well as a measure of extensibility by allowing / using more keys from the
subject
mapping in the future.Like
execute_kw
, calls then take aargs
list and akwargs
dict, both are optional rather than just the kwargs: a method can be called with only args, only kwargs, both or neither.Custom XMLRPC serializer (improves on 2)
New CC methods can return recordsets, which the new RPC endpoint has to automatically convert somehow, the easiest was to serialise recordsets as lists of ids (that's coherent with the old/new API decorators, and means e.g.
search
returns the same thing).Customizing
xmlrpclib.Marshaller
didn't work because it dispatches by strict type equality, so it wasn't possible to hook into it to serialise arbitrary recordsets (they get types created on-the-fly). A new marshaller was reimplemented on top of lxml instead. It provides the following features not provided byxmlrpclib.Marshaller
:BaseModel.ids
(ignores NewId)json.dumps
) instead of raising an error (see XmlRcp error: dictionary key must be string #12667 and probably others I didn't find)dict
list
andtuple
Allow None/nil/null (fixes 3)
Of the 4 libraries we have examples for, 3 either enable the feature by default or via a simple flag. The last one (Apache XML-RPC) can be coerced into supporting it using ~15 lines of code which is downright terse as far as the combination of Java and dynamically typed RPC goes.
This allows more natural returns from methods (aka don't return anything instead of the artificial unconditional
return True
andreturn False
peppered through the codebase) and is in line with JSON-RPC.Endpoint (avoids conflict with existing)
Uses
/RPC2
:path
is providedAuthentication (fixes 4, also improves on 1)
All xmlrpc libraries surveyed provide built-in support for Basic Authentication. The new endpoint thus replaces the custom authentication by BA
Leverage XML-RPC conventions (improves on 1)
XML-RPC
methodName
is conventionally a dotted path (e.g.system.listMethods
), which some libraries (ripcord
,xmlrpclib
) can map onto the language's own dereferencing/attribute access. This maps nicely to Odoo's models: the last segment of themethodName
is the method itself, the rest is the model name. This meansread
foraccount.account
can be called as:Ruby's stdlib XMLRPC::Client has no separation between dereferencing and method call, but it's still possible to proxy to a model then call a number of methods on it e.g.
Because authentication depends on the db (or lack thereof), the database name is part of the endpoint URL (query parameter)
methodName
without dots)methodName
Code comparison
setup
becomes
or in Ruby
becomes
simple method call
becomes
search
becomes
read
becomes
JSON-RPC ISO
The endpoint also supports JSON-RPC2 with the same signature, using basic auth. The dispatching is performed based on the request's mime type.
Nota: maybe the XML-RPC serialiser should reject datetimes and binaries? These are not types JSON understands...
Possible additions/reflexions
standard extension
system.multicall
, allows performing multiple independent calls over a single RPC requests, has somewhat built-in support on all reviewed RPC libraries (Python's stdlib, Ruby's stdlib, PHP's Ripcord and Java's Apache XML-RPC)similar but non-standard extensions
system.cascade
andsystem.sequence
, the first would allow a sequence of ~independent calls to the same subject, the latter would instead be a call chain, where call 2 would be performed on the result of call 1 e.g.would call "action_confirm()" then "read()" on the subject but
would get the subject's associated partners then read those.
/cc @odony @rco-odoo