[tools] Provide a faster build_user
authorChristophe de Vienne <christophe@unlish.com>
Mon, 26 Jan 2015 17:59:10 +0100
changeset 11550 38ed4c3ac3de
parent 11549 48a952dc108f
child 11551 444cd2bba89d
[tools] Provide a faster build_user The main trick is to use a cache of user entities. To do so, a few tools are needed since the entities are not supposed to be copied around between connexions. Related to #4870347
pyramid_cubicweb/__init__.py
pyramid_cubicweb/tests/test_tools.py
pyramid_cubicweb/tools.py
--- a/pyramid_cubicweb/__init__.py	Fri Jan 23 14:00:02 2015 +0100
+++ b/pyramid_cubicweb/__init__.py	Mon Jan 26 17:59:10 2015 +0100
@@ -54,6 +54,7 @@
     for name in aslist(config.registry.settings.get('cubicweb.includes', [])):
         config.include(name)
 
+    config.include('pyramid_cubicweb.tools')
     config.include('pyramid_cubicweb.core')
 
     if asbool(config.registry.settings.get('cubicweb.bwcompat', True)):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/tests/test_tools.py	Mon Jan 26 17:59:10 2015 +0100
@@ -0,0 +1,26 @@
+from pyramid_cubicweb.tests import PyramidCWTest
+from pyramid_cubicweb import tools
+
+
+class ToolsTest(PyramidCWTest):
+    anonymous_allowed = True
+
+    def test_clone_user(self):
+        with self.admin_access.repo_cnx() as cnx:
+            user = cnx.find('CWUser', login='anon').one()
+            user.login  # fill the cache
+            clone = tools.clone_user(self.repo, user)
+
+            self.assertEqual(clone.eid, user.eid)
+            self.assertEqual(clone.login, user.login)
+
+            self.assertEqual(clone.cw_rset.rows, user.cw_rset.rows)
+            self.assertEqual(clone.cw_rset.rql, user.cw_rset.rql)
+
+    def test_cnx_attach_entity(self):
+        with self.admin_access.repo_cnx() as cnx:
+            user = cnx.find('CWUser', login='anon').one()
+
+        with self.admin_access.repo_cnx() as cnx:
+            tools.cnx_attach_entity(cnx, user)
+            self.assertEqual(user.login, 'anon')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/tools.py	Mon Jan 26 17:59:10 2015 +0100
@@ -0,0 +1,64 @@
+"""Various tools"""
+
+#: A short-term cache for user clones.
+#: used by cached_build_user to speed-up repetitive calls to build_user
+#: The expiration is handled in a dumb and brutal way: the whole cache is
+#: cleared every 5 minutes.
+_user_cache = {}
+
+
+def clone_user(repo, user):
+    """Clone a CWUser instance.
+
+    .. warning::
+
+        The returned clone is detached from any cnx.
+        Before using it in any way, it should be attached to a cnx that has not
+        this user already loaded.
+    """
+    CWUser = repo.vreg['etypes'].etype_class('CWUser')
+    clone = CWUser(
+        None,
+        rset=user.cw_rset.copy(),
+        row=user.cw_row,
+        col=user.cw_col,
+        groups=set(user._groups) if hasattr(user, '_groups') else None,
+        properties=dict(user._properties)
+        if hasattr(user, '_properties') else None)
+    clone.cw_attr_cache = dict(user.cw_attr_cache)
+    return clone
+
+
+def cnx_attach_entity(cnx, entity):
+    """Attach an entity to a cnx."""
+    entity._cw = cnx
+    if entity.cw_rset:
+        entity.cw_rset.req = cnx
+
+
+def cached_build_user(repo, eid):
+    """Cached version of
+    :meth:`cubicweb.server.repository.Repository._build_user`
+    """
+    with repo.internal_cnx() as cnx:
+        if eid in _user_cache:
+            entity = clone_user(repo, _user_cache[eid])
+            cnx_attach_entity(cnx, entity)
+            return entity
+
+        user = repo._build_user(cnx, eid)
+        user.cw_clear_relation_cache()
+        _user_cache[eid] = clone_user(repo, user)
+        return user
+
+
+def clear_cache():
+    """Clear the user cache"""
+    _user_cache.clear()
+
+
+def includeme(config):
+    repo = config.registry['cubicweb.repository']
+    interval = int(config.registry.settings.get(
+        'cubicweb.usercache.expiration_time', 60*5))
+    repo.looping_task(interval, clear_cache)