From f0646cb51b236a3afd5e7289f5cd5986c88da8f6 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 27 Jan 2016 18:00:48 +0100 Subject: [PATCH] [IMP] fields: use a "lazy" cursor to compute attribute `digits` Borrowing a cursor each time you access `field.digits` may be costly, because of the connection reset. Moreover, in most cases, the cursor is not used at all, since the decimal precision are kept in cache. For the installation of module `product` with its demo data, the number of cursor allocations was reduced from ~1500 to about 50! --- openerp/fields.py | 5 +++-- openerp/osv/fields.py | 15 ++++++--------- openerp/sql_db.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/openerp/fields.py b/openerp/fields.py index a6775336a1e..b46e4e3cda7 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -30,6 +30,7 @@ import logging import pytz import xmlrpclib +from openerp.sql_db import LazyCursor from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT @@ -1070,7 +1071,7 @@ class Float(Field): @property def digits(self): if callable(self._digits): - with fields._get_cursor() as cr: + with LazyCursor() as cr: return self._digits(cr) else: return self._digits @@ -1882,7 +1883,7 @@ class Id(Field): raise TypeError("field 'id' cannot be assigned") # imported here to avoid dependency cycle issues -from openerp import SUPERUSER_ID, registry +from openerp import SUPERUSER_ID from .exceptions import Warning, AccessError, MissingError from .models import BaseModel, MAGIC_COLUMNS from .osv import fields diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 38b034dded1..54b39418b9c 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -43,21 +43,18 @@ import pytz import re import xmlrpclib from operator import itemgetter -from contextlib import contextmanager from psycopg2 import Binary import openerp import openerp.tools as tools +from openerp.sql_db import LazyCursor from openerp.tools.translate import _ from openerp.tools import float_repr, float_round, frozendict, html_sanitize import simplejson -from openerp import SUPERUSER_ID, registry +from openerp import SUPERUSER_ID -@contextmanager -def _get_cursor(): - # yield a valid cursor from any environment or create a new one if none found - with registry().cursor() as cr: - yield cr +# deprecated; kept for backward compatibility only +_get_cursor = LazyCursor EMPTY_DICT = frozendict() @@ -396,7 +393,7 @@ class float(_column): @property def digits(self): if self._digits_compute: - with _get_cursor() as cr: + with LazyCursor() as cr: return self._digits_compute(cr) else: return self._digits @@ -1323,7 +1320,7 @@ class function(_column): @property def digits(self): if self._digits_compute: - with _get_cursor() as cr: + with LazyCursor() as cr: return self._digits_compute(cr) else: return self._digits diff --git a/openerp/sql_db.py b/openerp/sql_db.py index f389a847548..a3af4be677a 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -443,6 +443,40 @@ class TestCursor(Cursor): self.execute("ROLLBACK TO SAVEPOINT test_cursor") self.execute("SAVEPOINT test_cursor") +class LazyCursor(object): + """ A proxy object to a cursor. The cursor itself is allocated only if it is + needed. This class is useful for cached methods, that use the cursor + only in the case of a cache miss. + """ + def __init__(self, dbname=None): + self._dbname = dbname + self._cursor = None + self._depth = 0 + + @property + def dbname(self): + return self._dbname or threading.currentThread().dbname + + def __getattr__(self, name): + cr = self._cursor + if cr is None: + from openerp import registry + cr = self._cursor = registry(self.dbname).cursor() + for _ in xrange(self._depth): + cr.__enter__() + return getattr(cr, name) + + def __enter__(self): + self._depth += 1 + if self._cursor is not None: + self._cursor.__enter__() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._depth -= 1 + if self._cursor is not None: + self._cursor.__exit__(exc_type, exc_value, traceback) + class PsycoConnection(psycopg2.extensions.connection): pass