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):

Reply via email to