changeset 34ec4c3b700c in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=34ec4c3b700c
description:
Add cache management on RPC
issue8233
review257231002
diffstat:
CHANGELOG | 1 +
doc/ref/rpc.rst | 24 ++++++++++++++++++++++-
trytond/ir/action.py | 4 ++-
trytond/ir/ui/view.py | 2 +-
trytond/model/model.py | 4 +-
trytond/model/modelview.py | 4 +-
trytond/protocols/dispatcher.py | 5 +++-
trytond/rpc.py | 21 ++++++++++++++++++-
trytond/wsgi.py | 42 +++++++++++++++++++++-------------------
9 files changed, 77 insertions(+), 30 deletions(-)
diffs (229 lines):
diff -r 691d0ad1b947 -r 34ec4c3b700c CHANGELOG
--- a/CHANGELOG Mon Apr 08 10:02:54 2019 +0200
+++ b/CHANGELOG Mon Apr 08 10:10:40 2019 +0200
@@ -1,3 +1,4 @@
+* Add cache on RPC
* Remove support for Python 3.4
* Allow to update record when importing data
* Set context on record instantiated by Field.__set__
diff -r 691d0ad1b947 -r 34ec4c3b700c doc/ref/rpc.rst
--- a/doc/ref/rpc.rst Mon Apr 08 10:02:54 2019 +0200
+++ b/doc/ref/rpc.rst Mon Apr 08 10:10:40 2019 +0200
@@ -5,7 +5,7 @@
RPC
===
-.. class:: RPC([readonly[, instantiate[, result[, check_access[, unique[,
fresh_session]]]]]])
+.. class:: RPC([readonly[, instantiate[, result[, check_access[, unique[,
fresh_session[, cache]]]]]]])
RPC is an object to define the behavior of Remote Procedure Call.
@@ -35,3 +35,25 @@
.. attribute:: RPC.fresh_session
If set, it requires a fresh session. Default is `False`.
+
+.. attribute:: RPC.cache
+
+ A :class:`RPCCache` instance to compute the cache duration for the answer.
+
+========
+RPCCache
+========
+
+.. class:: RPCCache([days[, seconds])
+
+Instance attributes are:
+
+.. attribute:: RPC.duration
+
+ A timdelta instance.
+
+Instance methods are:
+
+.. method:: RCP.headers
+
+ Returns a dictionary of the headers.
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/ir/action.py
--- a/trytond/ir/action.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/ir/action.py Mon Apr 08 10:10:40 2019 +0200
@@ -139,7 +139,9 @@
@classmethod
def __setup__(cls):
super(ActionKeyword, cls).__setup__()
- cls.__rpc__.update({'get_keyword': RPC()})
+ cls.__rpc__.update({
+ 'get_keyword': RPC(cache=dict(days=1)),
+ })
@classmethod
def __register__(cls, module_name):
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/ir/ui/view.py
--- a/trytond/ir/ui/view.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/ir/ui/view.py Mon Apr 08 10:10:40 2019 +0200
@@ -312,7 +312,7 @@
super(ViewTreeState, cls).__setup__()
cls.__rpc__.update({
'set': RPC(readonly=False, check_access=False),
- 'get': RPC(check_access=False),
+ 'get': RPC(check_access=False, cache=dict(days=1)),
})
@classmethod
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/model/model.py
--- a/trytond/model/model.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/model/model.py Mon Apr 08 10:10:40 2019 +0200
@@ -35,8 +35,8 @@
def __setup__(cls):
super(Model, cls).__setup__()
cls.__rpc__ = {
- 'default_get': RPC(),
- 'fields_get': RPC(),
+ 'default_get': RPC(cache=dict(seconds=5 * 60)),
+ 'fields_get': RPC(cache=dict(days=1)),
'pre_validate': RPC(instantiate=0),
}
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/model/modelview.py
--- a/trytond/model/modelview.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/model/modelview.py Mon Apr 08 10:10:40 2019 +0200
@@ -118,8 +118,8 @@
@classmethod
def __setup__(cls):
super(ModelView, cls).__setup__()
- cls.__rpc__['fields_view_get'] = RPC()
- cls.__rpc__['view_toolbar_get'] = RPC()
+ cls.__rpc__['fields_view_get'] = RPC(cache=dict(days=1))
+ cls.__rpc__['view_toolbar_get'] = RPC(cache=dict(days=1))
cls.__rpc__['on_change'] = RPC(instantiate=0)
cls.__rpc__['on_change_with'] = RPC(instantiate=0)
cls._buttons = {}
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/protocols/dispatcher.py
--- a/trytond/protocols/dispatcher.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/protocols/dispatcher.py Mon Apr 08 10:10:40 2019 +0200
@@ -214,4 +214,7 @@
context = {'_request': request.context}
security.reset(pool.database_name, session, context=context)
logger.debug('Result: %s', result)
- return result
+ response = app.make_response(request, result)
+ if rpc.readonly and rpc.cache:
+ response.headers.extend(rpc.cache.headers())
+ return response
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/rpc.py
--- a/trytond/rpc.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/rpc.py Mon Apr 08 10:10:40 2019 +0200
@@ -1,6 +1,7 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import copy
+import datetime as dt
from trytond.transaction import Transaction
@@ -19,10 +20,10 @@
'''
__slots__ = ('readonly', 'instantiate', 'result', 'check_access',
- 'fresh_session', 'unique')
+ 'fresh_session', 'unique', 'cache')
def __init__(self, readonly=True, instantiate=None, result=None,
- check_access=True, fresh_session=False, unique=True):
+ check_access=True, fresh_session=False, unique=True, cache=None):
self.readonly = readonly
self.instantiate = instantiate
if result is None:
@@ -31,6 +32,10 @@
self.check_access = check_access
self.fresh_session = fresh_session
self.unique = unique
+ if cache:
+ if not isinstance(cache, RPCCache):
+ cache = RPCCache(**cache)
+ self.cache = cache
def convert(self, obj, *args, **kwargs):
args = list(args)
@@ -70,3 +75,15 @@
if self.check_access:
context['_check_access'] = True
return args, kwargs, context, timestamp
+
+
+class RPCCache:
+ __slots__ = ('duration',)
+
+ def __init__(self, days=0, seconds=0):
+ self.duration = dt.timedelta(days=days, seconds=seconds)
+
+ def headers(self):
+ return {
+ 'X-Tryton-Cache': int(self.duration.total_seconds()),
+ }
diff -r 691d0ad1b947 -r 34ec4c3b700c trytond/wsgi.py
--- a/trytond/wsgi.py Mon Apr 08 10:02:54 2019 +0200
+++ b/trytond/wsgi.py Mon Apr 08 10:10:40 2019 +0200
@@ -116,6 +116,27 @@
response = rv
return response
+ def make_response(self, request, data):
+ for cls in self.protocols:
+ for mimetype, _ in request.accept_mimetypes:
+ if cls.content_type in mimetype:
+ response = cls.response(data, request)
+ break
+ else:
+ continue
+ break
+ else:
+ for cls in self.protocols:
+ if cls.content_type in request.environ.get('CONTENT_TYPE', ''):
+ response = cls.response(data, request)
+ break
+ else:
+ if isinstance(data, Exception):
+ response = InternalServerError(data)
+ else:
+ response = Response(data)
+ return response
+
def wsgi_app(self, environ, start_response):
for cls in self.protocols:
if cls.content_type in environ.get('CONTENT_TYPE', ''):
@@ -135,24 +156,7 @@
data = self.dispatch_request(request)
if not isinstance(data, (Response, HTTPException)):
- for cls in self.protocols:
- for mimetype, _ in request.accept_mimetypes:
- if cls.content_type in mimetype:
- response = cls.response(data, request)
- break
- else:
- continue
- break
- else:
- for cls in self.protocols:
- if cls.content_type in environ.get('CONTENT_TYPE', ''):
- response = cls.response(data, request)
- break
- else:
- if isinstance(data, Exception):
- response = InternalServerError(data)
- else:
- response = Response(data)
+ response = self.make_response(request, data)
else:
response = data
@@ -167,8 +171,6 @@
response.headers['Access-Control-Allow-Headers'] = headers
response.headers['Access-Control-Max-Age'] = config.getint(
'web', 'cache_timeout')
-
- # TODO custom process response
return response(environ, start_response)
def __call__(self, environ, start_response):