changeset 33faabc6ec78 in trytond:default details: https://hg.tryton.org/trytond?cmd=changeset&node=33faabc6ec78 description: Use singleton for TableHandler
issue11301 review366861002 diffstat: CHANGELOG | 2 + trytond/backend/postgresql/table.py | 86 +++++++++++++++++++----------------- trytond/backend/sqlite/table.py | 59 +++++++++++------------- trytond/backend/table.py | 16 ++++++- trytond/model/modelsql.py | 2 +- 5 files changed, 90 insertions(+), 75 deletions(-) diffs (357 lines): diff -r 33c8f05556b3 -r 33faabc6ec78 CHANGELOG --- a/CHANGELOG Sat May 07 10:58:45 2022 +0200 +++ b/CHANGELOG Sat May 07 11:35:14 2022 +0200 @@ -1,3 +1,5 @@ +* Use singleton for TableHandler + Version 6.4.0 - 2022-05-02 * Bug fixes (see mercurial logs for details) * Use unittest discover diff -r 33c8f05556b3 -r 33faabc6ec78 trytond/backend/postgresql/table.py --- a/trytond/backend/postgresql/table.py Sat May 07 10:58:45 2022 +0200 +++ b/trytond/backend/postgresql/table.py Sat May 07 11:35:14 2022 +0200 @@ -17,13 +17,12 @@ class TableHandler(TableHandlerInterface): namedatalen = 64 - def __init__(self, model, module_name=None, history=False): - super(TableHandler, self).__init__(model, - module_name=module_name, history=history) - self._columns = {} - self._constraints = [] - self._fk_deltypes = {} - self._indexes = [] + def _init(self, model, history=False): + super()._init(model, history=history) + self.__columns = None + self.__constraints = None + self.__fk_deltypes = None + self.__indexes = None transaction = Transaction() cursor = transaction.connection.cursor() @@ -50,7 +49,6 @@ Identifier(self.table_name)), (model.__doc__,)) - self._update_definitions(columns=True) if 'id' not in self._columns: if not self.history: cursor.execute( @@ -77,6 +75,7 @@ cursor.execute( SQL('ALTER TABLE {} ADD PRIMARY KEY(__id)') .format(Identifier(self.table_name))) + self._update_definitions(columns=True) else: default = "nextval('%s'::regclass)" % self.sequence_name if self.history: @@ -86,13 +85,14 @@ "ALTER __id SET DEFAULT nextval(%s::regclass)") .format(Identifier(self.table_name)), (self.sequence_name,)) + self._update_definitions(columns=True) if self._columns['id']['default'] != default: cursor.execute( SQL("ALTER TABLE {} " "ALTER id SET DEFAULT nextval(%s::regclass)") .format(Identifier(self.table_name)), (self.sequence_name,)) - self._update_definitions() + self._update_definitions(columns=True) @staticmethod def table_exist(table_name): @@ -140,13 +140,11 @@ 'Unable to rename column %s on table %s to %s.', old_name, self.table_name, new_name) - def _update_definitions(self, - columns=None, constraints=None, indexes=None): - if columns is None and constraints is None and indexes is None: - columns = constraints = indexes = True - cursor = Transaction().connection.cursor() - if columns: - self._columns = {} + @property + def _columns(self): + if self.__columns is None: + cursor = Transaction().connection.cursor() + self.__columns = {} # Fetch columns definitions from the table cursor.execute('SELECT ' 'column_name, udt_name, is_nullable, ' @@ -156,20 +154,24 @@ 'WHERE table_name = %s AND table_schema = %s', (self.table_name, self.table_schema)) for column, typname, nullable, size, default in cursor: - self._columns[column] = { + self.__columns[column] = { 'typname': typname, 'notnull': True if nullable == 'NO' else False, 'size': size, 'default': default, } + return self.__columns - if constraints: + @property + def _constraints(self): + if self.__constraints is None: + cursor = Transaction().connection.cursor() # fetch constraints for the table cursor.execute('SELECT constraint_name ' 'FROM information_schema.table_constraints ' 'WHERE table_name = %s AND table_schema = %s', (self.table_name, self.table_schema)) - self._constraints = [c for c, in cursor] + self.__constraints = [c for c, in cursor] # add nonstandard exclude constraint cursor.execute('SELECT c.conname ' @@ -183,8 +185,13 @@ "AND r.relkind IN ('r', 'p') " 'AND r.relname = %s AND nr.nspname = %s', (self.table_name, self.table_schema)) - self._constraints.extend((c for c, in cursor)) + self.__constraints.extend((c for c, in cursor)) + return self.__constraints + @property + def _fk_deltypes(self): + if self.__fk_deltypes is None: + cursor = Transaction().connection.cursor() cursor.execute('SELECT k.column_name, r.delete_rule ' 'FROM information_schema.key_column_usage AS k ' 'JOIN information_schema.referential_constraints AS r ' @@ -192,9 +199,13 @@ 'AND r.constraint_name = k.constraint_name ' 'WHERE k.table_name = %s AND k.table_schema = %s', (self.table_name, self.table_schema)) - self._fk_deltypes = dict(cursor) + self.__fk_deltypes = dict(cursor) + return self.__fk_deltypes - if indexes: + @property + def _indexes(self): + if self.__indexes is None: + cursor = Transaction().connection.cursor() # Fetch indexes defined for the table cursor.execute("SELECT cl2.relname " "FROM pg_index ind " @@ -203,17 +214,20 @@ "JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) " "WHERE cl.relname = %s AND n.nspname = %s", (self.table_name, self.table_schema)) - self._indexes = [l[0] for l in cursor] + self.__indexes = [l[0] for l in cursor] + return self.__indexes - @property - def _field2module(self): - cursor = Transaction().connection.cursor() - cursor.execute('SELECT f.name, f.module ' - 'FROM ir_model_field f ' - 'JOIN ir_model m on (f.model=m.id) ' - 'WHERE m.model = %s', - (self.object_name,)) - return dict(cursor) + def _update_definitions(self, + columns=None, constraints=None, indexes=None): + if columns is None and constraints is None and indexes is None: + columns = constraints = indexes = True + if columns: + self.__columns = None + if constraints: + self.__constraints = None + self.__fk_deltypes = None + if indexes: + self.__indexes = None def alter_size(self, column_name, column_type): cursor = Transaction().connection.cursor() @@ -427,11 +441,6 @@ params) self._update_definitions(indexes=True) elif action == 'remove': - if len(columns) == 1 and isinstance(columns[0], str): - if self._field2module.get(columns[0], - self.module_name) != self.module_name: - return - if index_name in self._indexes: cursor.execute(SQL('DROP INDEX {}').format( Identifier(index_name))) @@ -469,9 +478,6 @@ elif action == 'remove': if not self._columns[column_name]['notnull']: return - if (self._field2module.get(column_name, self.module_name) - != self.module_name): - return cursor.execute( SQL('ALTER TABLE {} ALTER COLUMN {} DROP NOT NULL') .format( diff -r 33c8f05556b3 -r 33faabc6ec78 trytond/backend/sqlite/table.py --- a/trytond/backend/sqlite/table.py Sat May 07 10:58:45 2022 +0200 +++ b/trytond/backend/sqlite/table.py Sat May 07 11:35:14 2022 +0200 @@ -4,6 +4,7 @@ import logging import re import warnings +from weakref import WeakKeyDictionary from trytond.backend.table import TableHandlerInterface from trytond.transaction import Transaction @@ -21,13 +22,12 @@ class TableHandler(TableHandlerInterface): - def __init__(self, model, module_name=None, history=False): - super(TableHandler, self).__init__(model, - module_name=module_name, history=history) - self._columns = {} - self._constraints = [] - self._fk_deltypes = {} - self._indexes = [] + __handlers = WeakKeyDictionary() + + def _init(self, model, history=False): + super()._init(model, history=history) + self.__columns = None + self.__indexes = None self._model = model cursor = Transaction().connection.cursor() @@ -143,14 +143,12 @@ 'Unable to rename column %s on table %s to %s.', old_name, self.table_name, new_name) - def _update_definitions(self, columns=None, indexes=None): - if columns is None and indexes is None: - columns = indexes = True - cursor = Transaction().connection.cursor() - # Fetch columns definitions from the table - if columns: + @property + def _columns(self): + if self.__columns is None: + cursor = Transaction().connection.cursor() cursor.execute('PRAGMA table_info("' + self.table_name + '")') - self._columns = {} + self.__columns = {} for _, column, type_, notnull, hasdef, _ in cursor: column = re.sub(r'^\"|\"$', '', column) match = re.match(r'(\w+)(\((.*?)\))?', type_) @@ -160,30 +158,32 @@ else: typname = type_.upper() size = None - self._columns[column] = { + self.__columns[column] = { 'notnull': notnull, 'hasdef': hasdef, 'size': size, 'typname': typname, } + return self.__columns - # Fetch indexes defined for the table - if indexes: + @property + def _indexes(self): + if self.__indexes is None: + cursor = Transaction().connection.cursor() try: cursor.execute('PRAGMA index_list("' + self.table_name + '")') except IndexError: # There is sometimes IndexError cursor.execute('PRAGMA index_list("' + self.table_name + '")') - self._indexes = [l[1] for l in cursor] + self.__indexes = [l[1] for l in cursor] + return self.__indexes - @property - def _field2module(self): - cursor = Transaction().connection.cursor() - cursor.execute('SELECT f.name, f.module ' - 'FROM ir_model_field f ' - 'JOIN ir_model m on (f.model=m.id) ' - 'WHERE m.model = ?', - (self.object_name,)) - return dict(cursor) + def _update_definitions(self, columns=None, indexes=None): + if columns is None and indexes is None: + columns = indexes = True + if columns: + self.__columns = None + if indexes: + self.__indexes = None def alter_size(self, column_name, column_type): self._recreate_table({column_name: {'size': column_type}}) @@ -324,11 +324,6 @@ params) self._update_definitions(indexes=True) elif action == 'remove': - if len(columns) == 1 and isinstance(columns[0], str): - if self._field2module.get(columns[0], - self.module_name) != self.module_name: - return - if index_name in self._indexes: cursor.execute('DROP INDEX %s' % (_escape_identifier(index_name),)) diff -r 33c8f05556b3 -r 33faabc6ec78 trytond/backend/table.py --- a/trytond/backend/table.py Sat May 07 10:58:45 2022 +0200 +++ b/trytond/backend/table.py Sat May 07 11:35:14 2022 +0200 @@ -1,6 +1,9 @@ # 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 hashlib +from weakref import WeakKeyDictionary + +from trytond.transaction import Transaction class TableHandlerInterface(object): @@ -8,8 +11,18 @@ Define generic interface to handle database table ''' namedatalen = None + __handlers = WeakKeyDictionary() - def __init__(self, model, module_name=None, history=False): + def __new__(cls, model, history=False): + transaction = Transaction() + handlers = cls.__handlers.setdefault(transaction, {}) + key = (model.__name__, history) + if key not in handlers: + instance = handlers[key] = super().__new__(cls) + instance._init(model, history=history) + return handlers[key] + + def _init(self, model, history=False): ''' :param model: the Model linked to the table :param module_name: the module name @@ -25,7 +38,6 @@ self.sequence_name = self.table_name + '___id_seq' else: self.sequence_name = self.table_name + '_id_seq' - self.module_name = module_name self.history = history @staticmethod diff -r 33c8f05556b3 -r 33faabc6ec78 trytond/model/modelsql.py --- a/trytond/model/modelsql.py Sat May 07 10:58:45 2022 +0200 +++ b/trytond/model/modelsql.py Sat May 07 11:35:14 2022 +0200 @@ -211,7 +211,7 @@ @classmethod def __table_handler__(cls, module_name=None, history=False): - return backend.TableHandler(cls, module_name, history=history) + return backend.TableHandler(cls, history=history) @classmethod def __register__(cls, module_name):