]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
move to inspect_getfullargspec
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Jan 2019 17:22:40 +0000 (12:22 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 15 Jan 2019 15:01:53 +0000 (10:01 -0500)
Replace inspect_getargspec with inspect_getfullargspec
including a compatibility fallback for Py2k and use
getfullargspec fully.

Change-Id: I92bce0aafc37ce1a360b4f61b75f5892d0911c7e

lib/sqlalchemy/event/attr.py
lib/sqlalchemy/event/legacy.py
lib/sqlalchemy/orm/collections.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/testing/exclusions.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/compat.py
lib/sqlalchemy/util/langhelpers.py
test/base/test_utils.py
test/orm/test_session.py

index f0f65e67010e1e11cec1fd58df7d495a98f3bf29..9dfa89809dc985d5504055c2260f58ed93b50baf 100644 (file)
@@ -81,9 +81,9 @@ class _ClsLevelDispatch(RefCollection):
 
     def __init__(self, parent_dispatch_cls, fn):
         self.name = fn.__name__
-        argspec = util.inspect_getargspec(fn)
+        argspec = util.inspect_getfullargspec(fn)
         self.arg_names = argspec.args[1:]
-        self.has_kw = bool(argspec.keywords)
+        self.has_kw = bool(argspec.varkw)
         self.legacy_signatures = list(
             reversed(
                 sorted(
index 049df81aafa7b163be69d6561ad9e9e6ff7ef534..93b6a1a9a78ac8896b3dd75cf0b24e05af9e8f8b 100644 (file)
@@ -32,7 +32,7 @@ def _wrap_fn_for_legacy(dispatch_collection, fn, argspec):
             has_kw = False
 
         if len(argnames) == len(argspec.args) and has_kw is bool(
-            argspec.keywords
+            argspec.varkw
         ):
 
             if conv:
@@ -140,7 +140,7 @@ def _version_signature_changes(parent_dispatch_cls, dispatch_collection):
         "    The :class:`.%(clsname)s.%(event_name)s` event now accepts the \n"
         "    arguments ``%(named_event_arguments)s%(has_kw_arguments)s``.\n"
         "    Support for listener functions which accept the previous \n"
-        "    argument signature(s) listed above as \"deprecated\" will be \n"
+        '    argument signature(s) listed above as "deprecated" will be \n'
         "    removed in a future release."
         % {
             "since": since,
@@ -171,6 +171,7 @@ def _augment_fn_docs(dispatch_collection, parent_dispatch_cls, fn):
         )
 
         text += _version_signature_changes(
-            parent_dispatch_cls, dispatch_collection)
+            parent_dispatch_cls, dispatch_collection
+        )
 
     return util.inject_docstring_text(fn.__doc__, text, 1)
index 427296ca28bafa14741b14770cbf3d07c56ed25c..1e561369fb97d2d6438150fe310d8afb945e7162 100644 (file)
@@ -106,7 +106,7 @@ through the adapter, allowing for some very sophisticated behavior.
 import operator
 import weakref
 
-from sqlalchemy.util.compat import inspect_getargspec
+from sqlalchemy.util.compat import inspect_getfullargspec
 from . import base
 from .. import exc as sa_exc
 from .. import util
@@ -433,7 +433,7 @@ class collection(object):
         "The :meth:`.collection.linker` handler is deprecated and will "
         "be removed in a future release.  Please refer to the "
         ":meth:`.AttributeEvents.init_collection` "
-        "and :meth:`.AttributeEvents.dispose_collection` event handlers. "
+        "and :meth:`.AttributeEvents.dispose_collection` event handlers. ",
     )
     def linker(fn):
         """Tag the method as a "linked to attribute" event handler.
@@ -463,7 +463,7 @@ class collection(object):
         "The :meth:`.collection.converter` method is deprecated and will "
         "be removed in a future release.  Please refer to the "
         ":class:`.AttributeEvents.bulk_replace` listener interface in "
-        "conjunction with the :func:`.event.listen` function."
+        "conjunction with the :func:`.event.listen` function.",
     )
     def converter(fn):
         """Tag the method as the collection converter.
@@ -1008,7 +1008,9 @@ def _instrument_membership_mutator(method, before, argument, after):
     adapter."""
     # This isn't smart enough to handle @adds(1) for 'def fn(self, (a, b))'
     if before:
-        fn_args = list(util.flatten_iterator(inspect_getargspec(method)[0]))
+        fn_args = list(
+            util.flatten_iterator(inspect_getfullargspec(method)[0])
+        )
         if isinstance(argument, int):
             pos_arg = argument
             named_arg = len(fn_args) > argument and fn_args[argument] or None
index d9e5f87a31e89f82e1dadb0b2ca0205b601dc5cc..e10744fc1b3a5e43dfe1d202b0fddf2b375c205a 100644 (file)
@@ -22,7 +22,7 @@ from .session import sessionmaker
 from .. import event
 from .. import exc
 from .. import util
-from ..util.compat import inspect_getargspec
+from ..util.compat import inspect_getfullargspec
 
 
 class InstrumentationEvents(event.Events):
@@ -639,7 +639,7 @@ class MapperEvents(event.Events):
                 meth = getattr(cls, identifier)
                 try:
                     target_index = (
-                        inspect_getargspec(meth)[0].index("target") - 1
+                        inspect_getfullargspec(meth)[0].index("target") - 1
                     )
                 except ValueError:
                     target_index = None
index bfb239dad0a415f315785228cd6717ce6f46f4d2..d205354cfff5a043224d6a653fb0e6d7ff355493 100644 (file)
@@ -13,7 +13,7 @@ import re
 from . import config
 from .. import util
 from ..util import decorator
-from ..util.compat import inspect_getargspec
+from ..util.compat import inspect_getfullargspec
 
 
 def skip_if(predicate, reason=None):
@@ -303,7 +303,7 @@ class SpecPredicate(Predicate):
 
 class LambdaPredicate(Predicate):
     def __init__(self, lambda_, description=None, args=None, kw=None):
-        spec = inspect_getargspec(lambda_)
+        spec = inspect_getfullargspec(lambda_)
         if not spec[0]:
             self.lambda_ = lambda db: lambda_()
         else:
index 7e9bedd835e8b97a3103d9c08b797730e7a2a8dc..f812671d34c809e45a68e9aeb3ff040811d3afd4 100644 (file)
@@ -53,7 +53,7 @@ from .compat import cmp  # noqa
 from .compat import cpython  # noqa
 from .compat import decode_backslashreplace  # noqa
 from .compat import dottedgetter  # noqa
-from .compat import inspect_getargspec  # noqa
+from .compat import inspect_getfullargspec  # noqa
 from .compat import int_types  # noqa
 from .compat import iterbytes  # noqa
 from .compat import itertools_filter  # noqa
index 05e31f7bd9489da674fe038b96eda42fb30b2073..abd36d9b07ab6a1bf8c2dd5b78f0e5532e7bd85c 100644 (file)
@@ -31,8 +31,17 @@ dottedgetter = operator.attrgetter
 namedtuple = collections.namedtuple
 next = next  # noqa
 
-ArgSpec = collections.namedtuple(
-    "ArgSpec", ["args", "varargs", "keywords", "defaults"]
+FullArgSpec = collections.namedtuple(
+    "FullArgSpec",
+    [
+        "args",
+        "varargs",
+        "varkw",
+        "defaults",
+        "kwonlyargs",
+        "kwonlydefaults",
+        "annotations",
+    ],
 )
 
 try:
@@ -98,9 +107,6 @@ if py3k:
     def cmp(a, b):
         return (a > b) - (a < b)
 
-    def inspect_getargspec(func):
-        return ArgSpec(*inspect_getfullargspec(func)[0:4])
-
     def reraise(tp, value, tb=None, cause=None):
         if cause is not None:
             assert cause is not value, "Same cause emitted"
@@ -130,7 +136,7 @@ else:
 
     from StringIO import StringIO  # noqa
     from cStringIO import StringIO as byte_buffer  # noqa
-    from inspect import getargspec as inspect_getfullargspec  # noqa
+    from inspect import getargspec as _getargspec
     from itertools import izip_longest as zip_longest  # noqa
     from urllib import quote  # noqa
     from urllib import quote_plus  # noqa
@@ -149,7 +155,8 @@ else:
     text_type = unicode  # noqa
     int_types = int, long  # noqa
 
-    inspect_getargspec = inspect_getfullargspec
+    def inspect_getfullargspec(func):
+        return FullArgSpec(*_getargspec(func)[0:4] + ([], None, {}))
 
     callable = callable  # noqa
     cmp = cmp  # noqa
@@ -286,6 +293,14 @@ if py35:
         return result
 
 
+elif py2k:
+    from inspect import formatargspec as _inspect_formatargspec
+
+    def inspect_formatargspec(*spec, **kw):
+        # convert for a potential FullArgSpec from compat.getfullargspec()
+        return _inspect_formatargspec(*spec[0:4], **kw)  # noqa
+
+
 else:
     from inspect import formatargspec as inspect_formatargspec  # noqa
 
index 27c4be0ec73b307405772411e5f59ff2ef6bebf8..56e1304e3a4f9e93075bdeb9128d56db575bc75a 100644 (file)
@@ -121,8 +121,9 @@ def decorator(target):
     """A signature-matching decorator factory."""
 
     def decorate(fn):
-        if not inspect.isfunction(fn):
+        if not inspect.isfunction(fn) and not inspect.ismethod(fn):
             raise Exception("not a decoratable function")
+
         spec = compat.inspect_getfullargspec(fn)
         names = tuple(spec[0]) + spec[1:3] + (fn.__name__,)
         targ_name, fn_name = _unique_symbols(names, "target", "fn")
@@ -242,6 +243,26 @@ class PluginLoader(object):
         self.impls[name] = load
 
 
+def _inspect_func_args(fn):
+    try:
+        co_varkeywords = inspect.CO_VARKEYWORDS
+    except AttributeError:
+        # https://docs.python.org/3/library/inspect.html
+        # The flags are specific to CPython, and may not be defined in other
+        # Python implementations. Furthermore, the flags are an implementation
+        # detail, and can be removed or deprecated in future Python releases.
+        spec = compat.inspect_getfullargspec(fn)
+        return spec[0], bool(spec[2])
+    else:
+        # use fn.__code__ plus flags to reduce method call overhead
+        co = fn.__code__
+        nargs = co.co_argcount
+        return (
+            list(co.co_varnames[:nargs]),
+            bool(co.co_flags & inspect.CO_VARKEYWORDS),
+        )
+
+
 def get_cls_kwargs(cls, _set=None):
     r"""Return the full set of inherited kwargs for the given `cls`.
 
@@ -250,7 +271,10 @@ def get_cls_kwargs(cls, _set=None):
     to pass along unrecognized keywords to its base classes, and the
     collection process is repeated recursively on each of the bases.
 
-    Uses a subset of inspect.getargspec() to cut down on method overhead.
+    Uses a subset of inspect.getfullargspec() to cut down on method overhead,
+    as this is used within the Core typing system to create copies of type
+    objects which is a performance-sensitive operation.
+
     No anonymous tuple arguments please !
 
     """
@@ -267,7 +291,7 @@ def get_cls_kwargs(cls, _set=None):
     )
 
     if has_init:
-        names, has_kw = inspect_func_args(ctr)
+        names, has_kw = _inspect_func_args(ctr)
         _set.update(names)
 
         if not has_kw and not toplevel:
@@ -282,26 +306,6 @@ def get_cls_kwargs(cls, _set=None):
     return _set
 
 
-try:
-    # TODO: who doesn't have this constant?
-    from inspect import CO_VARKEYWORDS
-
-    def inspect_func_args(fn):
-        co = fn.__code__
-        nargs = co.co_argcount
-        names = co.co_varnames
-        args = list(names[:nargs])
-        has_kw = bool(co.co_flags & CO_VARKEYWORDS)
-        return args, has_kw
-
-
-except ImportError:
-
-    def inspect_func_args(fn):
-        names, _, has_kw, _ = compat.inspect_getargspec(fn)
-        return names, bool(has_kw)
-
-
 def get_func_kwargs(func):
     """Return the set of legal kwargs for the given `func`.
 
@@ -310,7 +314,7 @@ def get_func_kwargs(func):
 
     """
 
-    return compat.inspect_getargspec(func)[0]
+    return compat.inspect_getfullargspec(func)[0]
 
 
 def get_callable_argspec(fn, no_self=False, _is_init=False):
@@ -326,26 +330,38 @@ def get_callable_argspec(fn, no_self=False, _is_init=False):
         raise TypeError("Can't inspect builtin: %s" % fn)
     elif inspect.isfunction(fn):
         if _is_init and no_self:
-            spec = compat.inspect_getargspec(fn)
-            return compat.ArgSpec(
-                spec.args[1:], spec.varargs, spec.keywords, spec.defaults
+            spec = compat.inspect_getfullargspec(fn)
+            return compat.FullArgSpec(
+                spec.args[1:],
+                spec.varargs,
+                spec.varkw,
+                spec.defaults,
+                spec.kwonlyargs,
+                spec.kwonlydefaults,
+                spec.annotations,
             )
         else:
-            return compat.inspect_getargspec(fn)
+            return compat.inspect_getfullargspec(fn)
     elif inspect.ismethod(fn):
         if no_self and (_is_init or fn.__self__):
-            spec = compat.inspect_getargspec(fn.__func__)
-            return compat.ArgSpec(
-                spec.args[1:], spec.varargs, spec.keywords, spec.defaults
+            spec = compat.inspect_getfullargspec(fn.__func__)
+            return compat.FullArgSpec(
+                spec.args[1:],
+                spec.varargs,
+                spec.varkw,
+                spec.defaults,
+                spec.kwonlyargs,
+                spec.kwonlydefaults,
+                spec.annotations,
             )
         else:
-            return compat.inspect_getargspec(fn.__func__)
+            return compat.inspect_getfullargspec(fn.__func__)
     elif inspect.isclass(fn):
         return get_callable_argspec(
             fn.__init__, no_self=no_self, _is_init=True
         )
     elif hasattr(fn, "__func__"):
-        return compat.inspect_getargspec(fn.__func__)
+        return compat.inspect_getfullargspec(fn.__func__)
     elif hasattr(fn, "__call__"):
         if inspect.ismethod(fn.__call__):
             return get_callable_argspec(fn.__call__, no_self=no_self)
@@ -390,8 +406,8 @@ def format_argspec_plus(fn, grouped=True):
     if compat.callable(fn):
         spec = compat.inspect_getfullargspec(fn)
     else:
-        # we accept an existing argspec...
         spec = fn
+
     args = compat.inspect_formatargspec(*spec)
     if spec[0]:
         self_arg = spec[0][0]
@@ -400,22 +416,15 @@ def format_argspec_plus(fn, grouped=True):
     else:
         self_arg = None
 
-    if compat.py3k:
-        apply_pos = compat.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 = compat.inspect_formatargspec(spec[0], spec[1], spec[2])
-        num_defaults = 0
-        if spec[3]:
-            num_defaults += len(spec[3])
-        name_args = spec[0]
+    apply_pos = compat.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]
 
     if num_defaults:
         defaulted_vals = name_args[0 - num_defaults :]
@@ -479,7 +488,7 @@ def getargspec_init(method):
 
     """
     try:
-        return compat.inspect_getargspec(method)
+        return compat.inspect_getfullargspec(method)
     except TypeError:
         if method is object.__init__:
             return (["self"], None, None, None)
@@ -516,30 +525,30 @@ def generic_repr(obj, additional_kw=(), to_inspect=None, omit_kwarg=()):
     vargs = None
     for i, insp in enumerate(to_inspect):
         try:
-            (_args, _vargs, vkw, defaults) = compat.inspect_getargspec(
-                insp.__init__
-            )
+            spec = compat.inspect_getfullargspec(insp.__init__)
         except TypeError:
             continue
         else:
-            default_len = defaults and len(defaults) or 0
+            default_len = spec.defaults and len(spec.defaults) or 0
             if i == 0:
-                if _vargs:
-                    vargs = _vargs
+                if spec.varargs:
+                    vargs = spec.varargs
                 if default_len:
-                    pos_args.extend(_args[1:-default_len])
+                    pos_args.extend(spec.args[1:-default_len])
                 else:
-                    pos_args.extend(_args[1:])
+                    pos_args.extend(spec.args[1:])
             else:
                 kw_args.update(
-                    [(arg, missing) for arg in _args[1:-default_len]]
+                    [(arg, missing) for arg in spec.args[1:-default_len]]
                 )
 
             if default_len:
                 kw_args.update(
                     [
                         (arg, default)
-                        for arg, default in zip(_args[-default_len:], defaults)
+                        for arg, default in zip(
+                            spec.args[-default_len:], spec.defaults
+                        )
                     ]
                 )
     output = []
@@ -710,7 +719,7 @@ def monkeypatch_proxied_specials(
         except AttributeError:
             continue
         try:
-            spec = compat.inspect_getargspec(fn)
+            spec = compat.inspect_getfullargspec(fn)
             fn_args = compat.inspect_formatargspec(spec[0])
             d_args = compat.inspect_formatargspec(spec[0][1:])
         except TypeError:
@@ -1478,8 +1487,8 @@ class EnsureKWArgType(type):
                 m = re.match(fn_reg, key)
                 if m:
                     fn = clsdict[key]
-                    spec = compat.inspect_getargspec(fn)
-                    if not spec.keywords:
+                    spec = compat.inspect_getfullargspec(fn)
+                    if not spec.varkw:
                         clsdict[key] = wrapped = cls._wrap_w_kw(fn)
                         setattr(cls, key, wrapped)
         super(EnsureKWArgType, cls).__init__(clsname, bases, clsdict)
index 69af6e03291fc4d14279546a54946fe35979b676..b27379248d3bfdc2546a32516fc16de7facf0f47 100644 (file)
@@ -1674,7 +1674,10 @@ class ArgInspectionTest(fixtures.TestBase):
         def foo(x, y, **kw):
             pass
 
-        eq_(get_callable_argspec(foo), (["x", "y"], None, "kw", None))
+        eq_(
+            get_callable_argspec(foo),
+            compat.FullArgSpec(["x", "y"], None, "kw", None, [], None, {}),
+        )
 
     def test_callable_argspec_fn_no_self(self):
         def foo(x, y, **kw):
@@ -1682,7 +1685,7 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(foo, no_self=True),
-            (["x", "y"], None, "kw", None),
+            compat.FullArgSpec(["x", "y"], None, "kw", None, [], None, {}),
         )
 
     def test_callable_argspec_fn_no_self_but_self(self):
@@ -1691,7 +1694,9 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(foo, no_self=True),
-            (["self", "x", "y"], None, "kw", None),
+            compat.FullArgSpec(
+                ["self", "x", "y"], None, "kw", None, [], None, {}
+            ),
         )
 
     @fails_if(lambda: util.pypy, "pypy returns plain *arg, **kw")
@@ -1711,7 +1716,9 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(Foo.foo),
-            (["self", "x", "y"], None, "kw", None),
+            compat.FullArgSpec(
+                ["self", "x", "y"], None, "kw", None, [], None, {}
+            ),
         )
 
     def test_callable_argspec_instance_method_no_self(self):
@@ -1721,7 +1728,7 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(Foo().foo, no_self=True),
-            (["x", "y"], None, "kw", None),
+            compat.FullArgSpec(["x", "y"], None, "kw", None, [], None, {}),
         )
 
     def test_callable_argspec_unbound_method_no_self(self):
@@ -1731,7 +1738,9 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(Foo.foo, no_self=True),
-            (["self", "x", "y"], None, "kw", None),
+            compat.FullArgSpec(
+                ["self", "x", "y"], None, "kw", None, [], None, {}
+            ),
         )
 
     def test_callable_argspec_init(self):
@@ -1739,7 +1748,12 @@ class ArgInspectionTest(fixtures.TestBase):
             def __init__(self, x, y):
                 pass
 
-        eq_(get_callable_argspec(Foo), (["self", "x", "y"], None, None, None))
+        eq_(
+            get_callable_argspec(Foo),
+            compat.FullArgSpec(
+                ["self", "x", "y"], None, None, None, [], None, {}
+            ),
+        )
 
     def test_callable_argspec_init_no_self(self):
         class Foo(object):
@@ -1748,7 +1762,7 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(Foo, no_self=True),
-            (["x", "y"], None, None, None),
+            compat.FullArgSpec(["x", "y"], None, None, None, [], None, {}),
         )
 
     def test_callable_argspec_call(self):
@@ -1757,7 +1771,10 @@ class ArgInspectionTest(fixtures.TestBase):
                 pass
 
         eq_(
-            get_callable_argspec(Foo()), (["self", "x", "y"], None, None, None)
+            get_callable_argspec(Foo()),
+            compat.FullArgSpec(
+                ["self", "x", "y"], None, None, None, [], None, {}
+            ),
         )
 
     def test_callable_argspec_call_no_self(self):
@@ -1767,7 +1784,7 @@ class ArgInspectionTest(fixtures.TestBase):
 
         eq_(
             get_callable_argspec(Foo(), no_self=True),
-            (["x", "y"], None, None, None),
+            compat.FullArgSpec(["x", "y"], None, None, None, [], None, {}),
         )
 
     @fails_if(lambda: util.pypy, "pypy returns plain *arg, **kw")
@@ -1781,6 +1798,25 @@ class ArgInspectionTest(fixtures.TestBase):
 
         assert_raises(TypeError, get_callable_argspec, bar)
 
+    def test_getargspec_6_tuple(self):
+        def foo(x, y, z, **kw):
+            pass
+
+        spec = compat.inspect_getfullargspec(foo)
+
+        eq_(
+            spec,
+            compat.FullArgSpec(
+                args=["x", "y", "z"],
+                varargs=None,
+                varkw="kw",
+                defaults=None,
+                kwonlyargs=[],
+                kwonlydefaults=None,
+                annotations={},
+            ),
+        )
+
 
 class SymbolTest(fixtures.TestBase):
     def test_basic(self):
@@ -1833,16 +1869,49 @@ class SymbolTest(fixtures.TestBase):
         assert (sym1 | sym2) & (sym2 | sym4)
 
 
-class TestFormatArgspec(fixtures.TestBase):
-    def test_specs(self):
-        def test(fn, wanted, grouped=None):
-            if grouped is None:
-                parsed = util.format_argspec_plus(fn)
-            else:
-                parsed = util.format_argspec_plus(fn, grouped=grouped)
-            eq_(parsed, wanted)
+class _Py3KFixtures(object):
+    pass
+
+
+if util.py3k:
+    _locals = {}
+    exec(
+        """
+def _kw_only_fixture(self, a, *, b, c):
+    pass
+
+def _kw_plus_posn_fixture(self, a, *args, b, c):
+    pass
+
+def _kw_opt_fixture(self, a, *, b, c="c"):
+    pass
+""",
+        _locals,
+    )
+    for k in _locals:
+        setattr(_Py3KFixtures, k, _locals[k])
+
 
-        test(
+class TestFormatArgspec(_Py3KFixtures, fixtures.TestBase):
+    def _test_format_argspec_plus(self, fn, wanted, grouped=None):
+
+        # test direct function
+        if grouped is None:
+            parsed = util.format_argspec_plus(fn)
+        else:
+            parsed = util.format_argspec_plus(fn, grouped=grouped)
+        eq_(parsed, wanted)
+
+        # test sending fullargspec
+        spec = compat.inspect_getfullargspec(fn)
+        if grouped is None:
+            parsed = util.format_argspec_plus(spec)
+        else:
+            parsed = util.format_argspec_plus(spec, grouped=grouped)
+        eq_(parsed, wanted)
+
+    def test_specs(self):
+        self._test_format_argspec_plus(
             lambda: None,
             {
                 "args": "()",
@@ -1852,13 +1921,13 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda: None,
             {"args": "", "self_arg": None, "apply_kw": "", "apply_pos": ""},
             grouped=False,
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda self: None,
             {
                 "args": "(self)",
@@ -1868,7 +1937,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda self: None,
             {
                 "args": "self",
@@ -1879,7 +1948,7 @@ class TestFormatArgspec(fixtures.TestBase):
             grouped=False,
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda *a: None,
             {
                 "args": "(*a)",
@@ -1889,7 +1958,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda **kw: None,
             {
                 "args": "(**kw)",
@@ -1899,7 +1968,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda *a, **kw: None,
             {
                 "args": "(*a, **kw)",
@@ -1909,7 +1978,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a, *b: None,
             {
                 "args": "(a, *b)",
@@ -1919,7 +1988,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a, **b: None,
             {
                 "args": "(a, **b)",
@@ -1929,7 +1998,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a, *b, **c: None,
             {
                 "args": "(a, *b, **c)",
@@ -1939,7 +2008,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a, b=1, **c: None,
             {
                 "args": "(a, b=1, **c)",
@@ -1949,7 +2018,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a=1, b=2: None,
             {
                 "args": "(a=1, b=2)",
@@ -1959,7 +2028,7 @@ class TestFormatArgspec(fixtures.TestBase):
             },
         )
 
-        test(
+        self._test_format_argspec_plus(
             lambda a=1, b=2: None,
             {
                 "args": "a=1, b=2",
@@ -1970,6 +2039,38 @@ class TestFormatArgspec(fixtures.TestBase):
             grouped=False,
         )
 
+        if util.py3k:
+            self._test_format_argspec_plus(
+                self._kw_only_fixture,
+                {
+                    "args": "self, a, *, b, c",
+                    "self_arg": "self",
+                    "apply_pos": "self, a, *, b, c",
+                    "apply_kw": "self, a, b=b, c=c",
+                },
+                grouped=False,
+            )
+            self._test_format_argspec_plus(
+                self._kw_plus_posn_fixture,
+                {
+                    "args": "self, a, *args, b, c",
+                    "self_arg": "self",
+                    "apply_pos": "self, a, *args, b, c",
+                    "apply_kw": "self, a, b=b, c=c, *args",
+                },
+                grouped=False,
+            )
+            self._test_format_argspec_plus(
+                self._kw_opt_fixture,
+                {
+                    "args": "self, a, *, b, c='c'",
+                    "self_arg": "self",
+                    "apply_pos": "self, a, *, b, c",
+                    "apply_kw": "self, a, b=b, c=c",
+                },
+                grouped=False,
+            )
+
     @testing.fails_if(
         lambda: util.pypy,
         "pypy doesn't report Obj.__init__ as object.__init__",
index 03b18df6aac1e9cb18f9102488097f8bbf7af8e0..7221f1d1268e04dfe31300b82d2fea58955fea71 100644 (file)
@@ -35,7 +35,7 @@ from sqlalchemy.testing.schema import Table
 from sqlalchemy.testing.util import gc_collect
 from sqlalchemy.util import pickle
 from sqlalchemy.util import pypy
-from sqlalchemy.util.compat import inspect_getargspec
+from sqlalchemy.util.compat import inspect_getfullargspec
 from test.orm import _fixtures
 
 
@@ -1850,7 +1850,7 @@ class SessionInterface(fixtures.TestBase):
         for meth in Session.public_methods:
             if meth in blacklist:
                 continue
-            spec = inspect_getargspec(getattr(Session, meth))
+            spec = inspect_getfullargspec(getattr(Session, meth))
             if len(spec[0]) > 1 or spec[1]:
                 ok.add(meth)
         return ok