]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
fix reference cycles/ perf in DialectKWArgs
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 26 Apr 2025 15:32:30 +0000 (11:32 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 26 Apr 2025 15:32:30 +0000 (11:32 -0400)
Identified some unnecessary cycles and overhead in how
this is implemented.  since we want to add this to Select,
needs these improvements.

Change-Id: I4324db14aaf52ab87a8b7fa49ebf1b6624bc2dcb

lib/sqlalchemy/sql/base.py
lib/sqlalchemy/util/langhelpers.py
test/aaa_profiling/test_memusage.py

index f867bfeb779c9d6d88a976044d431a0cc1c7d16d..38eea2d772df5dabfcd88ca59d5b976d90e448f2 100644 (file)
@@ -372,6 +372,8 @@ class _DialectArgView(MutableMapping[str, Any]):
 
     """
 
+    __slots__ = ("obj",)
+
     def __init__(self, obj):
         self.obj = obj
 
@@ -530,7 +532,7 @@ class DialectKWArgs:
             construct_arg_dictionary[cls] = {}
         construct_arg_dictionary[cls][argument_name] = default
 
-    @util.memoized_property
+    @property
     def dialect_kwargs(self):
         """A collection of keyword arguments specified as dialect-specific
         options to this construct.
@@ -558,14 +560,15 @@ class DialectKWArgs:
 
     _kw_registry = util.PopulateDict(_kw_reg_for_dialect)
 
-    def _kw_reg_for_dialect_cls(self, dialect_name):
+    @classmethod
+    def _kw_reg_for_dialect_cls(cls, dialect_name):
         construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
         d = _DialectArgDict()
 
         if construct_arg_dictionary is None:
             d._defaults.update({"*": None})
         else:
-            for cls in reversed(self.__class__.__mro__):
+            for cls in reversed(cls.__mro__):
                 if cls in construct_arg_dictionary:
                     d._defaults.update(construct_arg_dictionary[cls])
         return d
@@ -589,9 +592,7 @@ class DialectKWArgs:
 
         """
 
-        return util.PopulateDict(
-            util.portable_instancemethod(self._kw_reg_for_dialect_cls)
-        )
+        return util.PopulateDict(self._kw_reg_for_dialect_cls)
 
     def _validate_dialect_kwargs(self, kwargs: Dict[str, Any]) -> None:
         # validate remaining kwargs that they all specify DB prefixes
index 6c98504445eb8909c9a05ab8d5a6d6a27f6d5ba4..6868c81f5b502366e56b4ec046bb897d87134e22 100644 (file)
@@ -379,6 +379,9 @@ class PluginLoader:
 
         self.impls[name] = load
 
+    def deregister(self, name: str) -> None:
+        del self.impls[name]
+
 
 def _inspect_func_args(fn):
     try:
index 230832a714411247b5e9fedc103fa84e1082f1d0..01c1134538e1afa8c57367c94b6e8c5fd28968b0 100644 (file)
@@ -7,6 +7,7 @@ import weakref
 
 import sqlalchemy as sa
 from sqlalchemy import and_
+from sqlalchemy import ClauseElement
 from sqlalchemy import ForeignKey
 from sqlalchemy import func
 from sqlalchemy import inspect
@@ -20,8 +21,10 @@ from sqlalchemy import Unicode
 from sqlalchemy import util
 from sqlalchemy.dialects import mysql
 from sqlalchemy.dialects import postgresql
+from sqlalchemy.dialects import registry
 from sqlalchemy.dialects import sqlite
 from sqlalchemy.engine import result
+from sqlalchemy.engine.default import DefaultDialect
 from sqlalchemy.engine.processors import to_decimal_processor_factory
 from sqlalchemy.orm import aliased
 from sqlalchemy.orm import attributes
@@ -39,6 +42,7 @@ from sqlalchemy.orm import subqueryload
 from sqlalchemy.orm.session import _sessions
 from sqlalchemy.sql import column
 from sqlalchemy.sql import util as sql_util
+from sqlalchemy.sql.base import DialectKWArgs
 from sqlalchemy.sql.util import visit_binary_product
 from sqlalchemy.sql.visitors import cloned_traverse
 from sqlalchemy.sql.visitors import replacement_traverse
@@ -1136,6 +1140,22 @@ class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed):
             metadata.drop_all(self.engine)
 
 
+class SomeFoo(DialectKWArgs, ClauseElement):
+    pass
+
+
+class FooDialect(DefaultDialect):
+    construct_arguments = [
+        (
+            SomeFoo,
+            {
+                "bar": False,
+                "bat": False,
+            },
+        )
+    ]
+
+
 @testing.add_to_marker.memory_intensive
 class CycleTest(_fixtures.FixtureTest):
     __requires__ = ("cpython", "no_windows")
@@ -1160,6 +1180,33 @@ class CycleTest(_fixtures.FixtureTest):
 
         go()
 
+    @testing.fixture
+    def foo_dialect(self):
+        registry.register("foo", __name__, "FooDialect")
+
+        yield
+        registry.deregister("foo")
+
+    def test_dialect_kwargs(self, foo_dialect):
+
+        @assert_cycles()
+        def go():
+            ff = SomeFoo()
+
+            ff._validate_dialect_kwargs({"foo_bar": True})
+
+            eq_(ff.dialect_options["foo"]["bar"], True)
+
+            eq_(ff.dialect_options["foo"]["bat"], False)
+
+            eq_(ff.dialect_kwargs["foo_bar"], True)
+            eq_(ff.dialect_kwargs["foo_bat"], False)
+
+            ff.dialect_kwargs["foo_bat"] = True
+            eq_(ff.dialect_options["foo"]["bat"], True)
+
+        go()
+
     def test_session_execute_orm(self):
         User, Address = self.classes("User", "Address")
         configure_mappers()