From: Mike Bayer Date: Sat, 26 Apr 2025 15:32:30 +0000 (-0400) Subject: fix reference cycles/ perf in DialectKWArgs X-Git-Tag: rel_2_0_41~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b74b004ea124c365a5f2ec3828792e3433717f2c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git fix reference cycles/ perf in DialectKWArgs 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) --- diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 7ccef84e0d..8b9883ff1c 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -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 diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index a5ec5190f1..ae640c5ec2 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -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: diff --git a/test/aaa_profiling/test_memusage.py b/test/aaa_profiling/test_memusage.py index fc6be0f096..69952ac189 100644 --- a/test/aaa_profiling/test_memusage.py +++ b/test/aaa_profiling/test_memusage.py @@ -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()