From 575f080850a0a061ccb7ac40e3ea1fbf6b0fedf4 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 20 Jul 2015 20:35:04 -0400 Subject: [PATCH] - Fixed an issue where a particular base class within utils didn't implement ``__slots__``, and therefore meant all subclasses of that class didn't either, negating the rationale for ``__slots__`` to be in use. Didn't cause any issue except on IronPython which apparently does not implement ``__slots__`` behavior compatibly with cPython. Fixes #3494 --- doc/build/changelog/changelog_10.rst | 15 ++++++++++++++ lib/sqlalchemy/__init__.py | 2 +- lib/sqlalchemy/event/attr.py | 14 ++++++------- lib/sqlalchemy/orm/properties.py | 2 +- lib/sqlalchemy/orm/strategies.py | 3 ++- lib/sqlalchemy/util/langhelpers.py | 2 ++ test/base/test_utils.py | 30 +++++++++++++++++++++++++++- 7 files changed, 57 insertions(+), 11 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 4fdb509456..64bce97ae4 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -15,6 +15,21 @@ .. include:: changelog_07.rst :start-line: 5 +.. changelog:: + :version: 1.0.8 + + .. change:: + :tags: bug, misc + :tickets: 3494 + + Fixed an issue where a particular base class within utils + didn't implement ``__slots__``, and therefore meant all subclasses + of that class didn't either, negating the rationale for ``__slots__`` + to be in use. Didn't cause any issue except on IronPython + which apparently does not implement ``__slots__`` behavior compatibly + with cPython. + + .. changelog:: :version: 1.0.7 :released: July 20, 2015 diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 093e90bbf2..9b8d06167c 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -120,7 +120,7 @@ from .schema import ( from .inspection import inspect from .engine import create_engine, engine_from_config -__version__ = '1.0.7' +__version__ = '1.0.8' def __go(lcls): diff --git a/lib/sqlalchemy/event/attr.py b/lib/sqlalchemy/event/attr.py index a64c7d08d2..8a88e40efc 100644 --- a/lib/sqlalchemy/event/attr.py +++ b/lib/sqlalchemy/event/attr.py @@ -51,7 +51,7 @@ class _ClsLevelDispatch(RefCollection): """Class-level events on :class:`._Dispatch` classes.""" __slots__ = ('name', 'arg_names', 'has_kw', - 'legacy_signatures', '_clslevel') + 'legacy_signatures', '_clslevel', '__weakref__') def __init__(self, parent_dispatch_cls, fn): self.name = fn.__name__ @@ -230,9 +230,7 @@ class _EmptyListener(_InstanceLevelDispatch): class _CompoundListener(_InstanceLevelDispatch): - _exec_once = False - - __slots__ = '_exec_once_mutex', + __slots__ = '_exec_once_mutex', '_exec_once' def _memoized_attr__exec_once_mutex(self): return threading.Lock() @@ -279,11 +277,14 @@ class _ListenerCollection(_CompoundListener): """ - __slots__ = 'parent_listeners', 'parent', 'name', 'listeners', 'propagate' + __slots__ = ( + 'parent_listeners', 'parent', 'name', 'listeners', + 'propagate', '__weakref__') def __init__(self, parent, target_cls): if target_cls not in parent._clslevel: parent.update_subclass(target_cls) + self._exec_once = False self.parent_listeners = parent._clslevel[target_cls] self.parent = parent self.name = parent.name @@ -339,11 +340,10 @@ class _ListenerCollection(_CompoundListener): class _JoinedListener(_CompoundListener): - _exec_once = False - __slots__ = 'parent', 'name', 'local', 'parent_listeners' def __init__(self, parent, name, local): + self._exec_once = False self.parent = parent self.name = name self.local = local diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 55e02984b5..b1f1c61c4f 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -39,7 +39,7 @@ class ColumnProperty(StrategizedProperty): 'instrument', 'comparator_factory', 'descriptor', 'extension', 'active_history', 'expire_on_flush', 'info', 'doc', 'strategy_class', '_creation_order', '_is_polymorphic_discriminator', - '_mapped_by_synonym', '_deferred_loader') + '_mapped_by_synonym', '_deferred_column_loader') def __init__(self, *columns, **kwargs): """Provide a column-level property for use with a Mapper. diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 78e9293458..b9ef5808bb 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -361,7 +361,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): __slots__ = ( '_lazywhere', '_rev_lazywhere', 'use_get', '_bind_to_col', - '_equated_columns', '_rev_bind_to_col', '_rev_equated_columns') + '_equated_columns', '_rev_bind_to_col', '_rev_equated_columns', + '_simple_lazy_clause') def __init__(self, parent): super(LazyLoader, self).__init__(parent) diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 499515142a..dd25892430 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -805,6 +805,8 @@ class MemoizedSlots(object): """ + __slots__ = () + def _fallback_getattr(self, key): raise AttributeError(key) diff --git a/test/base/test_utils.py b/test/base/test_utils.py index 256f52850c..8074de53ed 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -2,13 +2,14 @@ import copy from sqlalchemy import util, sql, exc, testing from sqlalchemy.testing import assert_raises, assert_raises_message, fixtures -from sqlalchemy.testing import eq_, is_, ne_, fails_if +from sqlalchemy.testing import eq_, is_, ne_, fails_if, mock from sqlalchemy.testing.util import picklers, gc_collect from sqlalchemy.util import classproperty, WeakSequence, get_callable_argspec from sqlalchemy.sql import column from sqlalchemy.util import langhelpers import inspect + class _KeyedTupleTest(object): def _fixture(self, values, labels): @@ -284,6 +285,33 @@ class MemoizedAttrTest(fixtures.TestBase): eq_(f1.bar(), 20) eq_(val[0], 21) + def test_memoized_slots(self): + canary = mock.Mock() + + class Foob(util.MemoizedSlots): + __slots__ = ('foo_bar', 'gogo') + + def _memoized_method_gogo(self): + canary.method() + return "gogo" + + def _memoized_attr_foo_bar(self): + canary.attr() + return "foobar" + + f1 = Foob() + assert_raises(AttributeError, setattr, f1, "bar", "bat") + + eq_(f1.foo_bar, "foobar") + + eq_(f1.foo_bar, "foobar") + + eq_(f1.gogo(), "gogo") + + eq_(f1.gogo(), "gogo") + + eq_(canary.mock_calls, [mock.call.attr(), mock.call.method()]) + class ToListTest(fixtures.TestBase): def test_from_string(self): -- 2.47.3