Adjusted the check in the mapper for a callable object that is used as a
``@validates`` validator function or a ``@reconstructor`` reconstruction
function, to check for "callable" more liberally such as to accommodate
objects based on fundamental attributes like ``__func__`` and
``__call___``, rather than testing for ``MethodType`` / ``FunctionType``,
allowing things like cython functions to work properly. Pull request
courtesy Miłosz Stypiński.
Fixes: #6538
Closes: #6539
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/6539
Pull-request-sha:
ed1d7fe5c9386bab0416ff32095afc777c26b6ca
Change-Id: I8350558bc9a9ba58f43e48e12ce25a0b30e4d767
--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 6538
+
+ Adjusted the check in the mapper for a callable object that is used as a
+ ``@validates`` validator function or a ``@reconstructor`` reconstruction
+ function, to check for "callable" more liberally such as to accommodate
+ objects based on fundamental attributes like ``__func__`` and
+ ``__call___``, rather than testing for ``MethodType`` / ``FunctionType``,
+ allowing things like cython functions to work properly. Pull request
+ courtesy Miłosz Stypiński.
from collections import deque
from itertools import chain
import sys
-import types
import weakref
from . import attributes
for key, method in util.iterate_attributes(self.class_):
if key == "__init__" and hasattr(method, "_sa_original_init"):
method = method._sa_original_init
- if isinstance(method, types.MethodType):
+ if hasattr(method, "__func__"):
method = method.__func__
- if isinstance(method, types.FunctionType):
+ if callable(method):
if hasattr(method, "__sa_reconstructor__"):
self._reconstructor = method
event.listen(manager, "load", _event_on_load, raw=True)
fixture_session().query(User).first()
eq_(recon, ["go"])
+ def test_reconstructor_init_callable_object(self):
+ """test #6538"""
+
+ users = self.tables.users
+
+ recon = []
+
+ class recon_obj(object):
+ def __call__(self, obj):
+ recon.append("go")
+
+ class User(object):
+ __init__ = reconstructor(recon_obj())
+
+ self.mapper(User, users)
+
+ User()
+ eq_(recon, ["go"])
+
+ recon[:] = []
+ fixture_session().query(User).first()
+ eq_(recon, ["go"])
+
+ def test_reconstructor_init_simulate_cython(self):
+ """test #6538
+
+ This test is pretty contrived in order to ensure we aren't using
+ ``isinstance(obj, MethodType)`` within the mapper.
+
+ """
+
+ users = self.tables.users
+
+ recon = []
+
+ class recon_obj(object):
+ def __call__(self, obj):
+ recon.append("go")
+
+ __sa_reconstructor__ = True
+
+ class recon_meth(object):
+ __func__ = recon_obj()
+
+ def __call__(self, *arg, **kw):
+ return self.__func__.__call__(*arg, **kw)
+
+ class User(object):
+ __init__ = recon_meth()
+
+ self.mapper(User, users)
+
+ User()
+ eq_(recon, ["go"])
+
+ recon[:] = []
+ fixture_session().query(User).first()
+ eq_(recon, ["go"])
+
def test_reconstructor_init_inheritance(self):
users = self.tables.users
},
)
+ def test_validator_as_callable_object(self):
+ """test #6538"""
+ users = self.tables.users
+ canary = Mock()
+
+ class SomeValidator(object):
+ def __call__(self, obj, key, name):
+ canary(key, name)
+ ne_(name, "fred")
+ return name + " modified"
+
+ class User(fixtures.ComparableEntity):
+ sv = validates("name")(SomeValidator())
+
+ mapper(User, users)
+ u1 = User(name="ed")
+ eq_(u1.name, "ed modified")
+
def test_validator_multi_warning(self):
users = self.tables.users