Commits

Mike Bayer  committed e42476d

- Enhanced the instrumentation in the ORM to support
Py3K's new argument style of "required kw arguments",
i.e. fn(a, b, *, c, d), fn(a, b, *args, c, d).
Argument signatures of mapped object's __init__
method will be preserved, including required kw rules.
[ticket:2237]

  • Participants
  • Parent commits 865f78e

Comments (0)

Files changed (6)

     attribute, provided the name is the same as that
     of the entity mapped column.
 
+  - Enhanced the instrumentation in the ORM to support
+    Py3K's new argument style of "required kw arguments",
+    i.e. fn(a, b, *, c, d), fn(a, b, *args, c, d).
+    Argument signatures of mapped object's __init__
+    method will be preserved, including required kw rules.
+    [ticket:2237]
+
   - Fixed bug in unit of work whereby detection of 
     "cycles" among classes in highly interlinked patterns
     would not produce a deterministic

File lib/sqlalchemy/orm/instrumentation.py

 
     # Py3K
     #func_defaults = getattr(original__init__, '__defaults__', None)
+    #func_kw_defaults = getattr(original__init__, '__kwdefaults__', None)
     # Py2K
     func = getattr(original__init__, 'im_func', original__init__)
     func_defaults = getattr(func, 'func_defaults', None)
     __init__.__doc__ = original__init__.__doc__
     if func_defaults:
         __init__.func_defaults = func_defaults
+    # Py3K
+    #if func_kw_defaults:
+    #    __init__.__kwdefaults__ = func_kw_defaults
     return __init__

File lib/sqlalchemy/util/compat.py

     from urlparse import parse_qsl
 
 if py3k:
+    from inspect import getfullargspec as inspect_getfullargspec
+else:
+    from inspect import getargspec as inspect_getfullargspec
+
+if py3k:
     # they're bringing it back in 3.2.  brilliant !
     def callable(fn):
         return hasattr(fn, '__call__')

File lib/sqlalchemy/util/langhelpers.py

 import sys
 import types
 import warnings
-from compat import update_wrapper, set_types, threading
+from compat import update_wrapper, set_types, threading, callable, inspect_getfullargspec, py3k
 from sqlalchemy import exc
 
 def _unique_symbols(used, *bases):
     def decorate(fn):
         if not inspect.isfunction(fn):
             raise Exception("not a decoratable function")
-        spec = inspect.getargspec(fn)
+        spec = inspect_getfullargspec(fn)
         names = tuple(spec[0]) + spec[1:3] + (fn.func_name,)
         targ_name, fn_name = _unique_symbols(names, 'target', 'fn')
 
        'apply_pos': '(self, a, b, c, **d)'}
 
     """
-    spec = callable(fn) and inspect.getargspec(fn) or fn
+    if callable(fn):
+        spec = inspect_getfullargspec(fn)
+    else:
+        # we accept an existing argspec...
+        spec = fn
     args = inspect.formatargspec(*spec)
     if spec[0]:
         self_arg = spec[0][0]
         self_arg = '%s[0]' % spec[1]
     else:
         self_arg = None
-    apply_pos = inspect.formatargspec(spec[0], spec[1], spec[2])
-    defaulted_vals = spec[3] is not None and spec[0][0-len(spec[3]):] or ()
-    apply_kw = inspect.formatargspec(spec[0], spec[1], spec[2], defaulted_vals,
+
+    if py3k:
+        apply_pos = inspect.formatargspec(spec[0], spec[1], spec[2], None, spec[4])
+        num_defaults = 0
+        if spec[3]:
+            num_defaults += len(spec[3])
+        if spec[4]:
+            num_defaults += len(spec[4])
+        name_args = spec[0] + spec[4]
+    else:
+        apply_pos = inspect.formatargspec(spec[0], spec[1], spec[2])
+        num_defaults = 0
+        if spec[3]:
+            num_defaults += len(spec[3])
+        name_args = spec[0]
+
+    if num_defaults:
+        defaulted_vals = name_args[0-num_defaults:]
+    else:
+        defaulted_vals = ()
+
+    apply_kw = inspect.formatargspec(name_args, spec[1], spec[2], defaulted_vals,
                                      formatvalue=lambda x: '=' + x)
     if grouped:
         return dict(args=args, self_arg=self_arg,

File test/lib/requires.py

             )
     )
 
+def python3(fn):
+    return _chain_decorators_on(
+        fn,
+        skip_if(
+            lambda: sys.version_info < (3,),
+            "Python version 3.xx is required."
+            )
+    )
+
 def python26(fn):
     return _chain_decorators_on(
         fn,

File test/orm/test_instrumentation.py

 from test.lib.schema import Column
 from test.lib.testing import eq_, ne_
 from test.lib.util import decorator
-from test.lib import fixtures
+from test.lib import fixtures, testing
 
 @decorator
 def modifies_instrumentation_finders(fn, *args, **kw):
         class T(object): pass
         assert_raises(KeyError, mapper, T, t)
 
+class Py3KFunctionInstTest(fixtures.ORMTest):
+    __requires__ = ("python3", )
+
+    # Py3K
+    #def _kw_only_fixture(self):
+    #    class A(object):
+    #        def __init__(self, a, *, b, c):
+    #            self.a = a
+    #            self.b = b
+    #            self.c = c
+    #    return self._instrument(A)
+    #
+    #def _kw_plus_posn_fixture(self):
+    #    class A(object):
+    #        def __init__(self, a, *args, b, c):
+    #            self.a = a
+    #            self.b = b
+    #            self.c = c
+    #    return self._instrument(A)
+    #
+    #def _kw_opt_fixture(self):
+    #    class A(object):
+    #        def __init__(self, a, *, b, c="c"):
+    #            self.a = a
+    #            self.b = b
+    #            self.c = c
+    #    return self._instrument(A)
+
+    def _instrument(self, cls):
+        manager = instrumentation.register_class(cls)
+        canary = []
+        def check(target, args, kwargs):
+            canary.append((args, kwargs))
+        event.listen(manager, "init", check)
+        return cls, canary
+
+    def test_kw_only_args(self):
+        cls, canary = self._kw_only_fixture()
+
+        a = cls("a", b="b", c="c")
+        eq_(canary, [(('a', ), {'b':'b','c':'c'})])
+
+    def test_kw_plus_posn_args(self):
+        cls, canary = self._kw_plus_posn_fixture()
+
+        a = cls("a", 1, 2, 3, b="b", c="c")
+        eq_(canary, [(('a', 1, 2, 3), {'b':'b','c':'c'})])
+
+    def test_kw_only_args_plus_opt(self):
+        cls, canary = self._kw_opt_fixture()
+
+        a = cls("a", b="b")
+        eq_(canary, [(('a', ), {'b':'b','c':'c'})])
+
+        canary[:] = []
+        a = cls("a", b="b", c="d")
+        eq_(canary, [(('a', ), {'b':'b','c':'d'})])
+
+    def test_kw_only_sig(self):
+        cls, canary = self._kw_only_fixture()
+        assert_raises(
+            TypeError,
+            cls, "a", "b", "c"
+        )
+
+    def test_kw_plus_opt_sig(self):
+        cls, canary = self._kw_only_fixture()
+        assert_raises(
+            TypeError,
+            cls, "a", "b", "c"
+        )
+
+        assert_raises(
+            TypeError,
+            cls, "a", "b", c="c"
+        )
 
 class MiscTest(fixtures.ORMTest):
     """Seems basic, but not directly covered elsewhere!"""