]> 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:33:56 +0000 (11:33 -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
(cherry picked from commit ce3bbfcc4550e72a603640e533bc736715c5d76b)

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

index 7ccef84e0d54902d1b450077e22d14f498a0cc74..8b9883ff1c931a6ff3e3f96e89faa05f2874b184 100644 (file)
@@ -363,6 +363,8 @@ class _DialectArgView(MutableMapping[str, Any]):
 
     """
 
+    __slots__ = ("obj",)
+
     def __init__(self, obj):
         self.obj = obj
 
@@ -521,7 +523,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.
@@ -549,14 +551,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
@@ -580,9 +583,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 a5ec5190f1412b854789aa08ccf01d7e2c36a4c2..ae640c5ec2806110617a6a53336c959426b97b77 100644 (file)
@@ -385,6 +385,9 @@ class PluginLoader:
 
         self.impls[name] = load
 
+    def deregister(self, name: str) -> None:
+        del self.impls[name]
+
 
 def _inspect_func_args(fn):
     try:
index fc6be0f0960b283ffc33e9889b01f40e45821a27..69952ac18900cfe971de3a07bba23d3045e17883 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
@@ -1192,6 +1196,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")
@@ -1216,6 +1236,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()