Merge lp:~cjwatson/storm/py3-mocker-inspect into lp:storm

Proposed by Colin Watson on 2019-08-11
Status: Merged
Merged at revision: 525
Proposed branch: lp:~cjwatson/storm/py3-mocker-inspect
Merge into: lp:storm
Diff against target: 96 lines (+49/-22)
1 file modified
storm/tests/mocker.py (+49/-22)
To merge this branch: bzr merge lp:~cjwatson/storm/py3-mocker-inspect
Reviewer Review Type Date Requested Status
Simon Poirier 2019-08-11 Approve on 2019-08-15
Review via email: mp+371174@code.launchpad.net

Commit message

Add Python 3 support to tests.mocker.SpecChecker.

Description of the change

inspect.getargspec is deprecated on Python 3, and in any case causes us problems because it includes the bound first argument (self or similar) for bound methods. The modern inspect.signature API is for the most part much easier to use and doesn't have this problem.

There is one wrinkle which requires special handling: method descriptors don't have the first argument already bound as far as their signature is concerned, but for the purposes of a mock spec we want to skip it anyway.

To post a comment you must log in.
Simon Poirier (simpoir) wrote :

+1 Not the prettiest change, but seems ok.

review: Approve
513. By Colin Watson on 2019-08-21

Explain positional-only test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'storm/tests/mocker.py'
2--- storm/tests/mocker.py 2019-08-21 09:40:30 +0000
3+++ storm/tests/mocker.py 2019-08-21 11:14:10 +0000
4@@ -1830,22 +1830,43 @@
5
6 if method:
7 try:
8- self._args, self._varargs, self._varkwargs, self._defaults = \
9- inspect.getargspec(method)
10+ if six.PY3:
11+ # On Python 3, inspect.getargspec includes the bound
12+ # first argument (self or similar) for bound methods,
13+ # which confuses matters. The modern signature API
14+ # doesn't have this problem.
15+ self._signature = inspect.signature(method)
16+ # Method descriptors don't have the first argument
17+ # already bound, but we want to skip it anyway.
18+ if getattr(method, "__objclass__", None) is not None:
19+ parameters = list(self._signature.parameters.values())
20+ # This is positional-only for unbound methods that
21+ # are implemented in C.
22+ if (parameters[0].kind ==
23+ inspect.Parameter.POSITIONAL_ONLY):
24+ self._signature = self._signature.replace(
25+ parameters=parameters[1:])
26+ else:
27+ (self._args, self._varargs, self._varkwargs,
28+ self._defaults) = inspect.getargspec(method)
29 except TypeError:
30 self._unsupported = True
31 else:
32- if self._defaults is None:
33- self._defaults = ()
34- if type(method) is type(self.run):
35- self._args = self._args[1:]
36+ if not six.PY3:
37+ if self._defaults is None:
38+ self._defaults = ()
39+ if type(method) is type(self.run):
40+ self._args = self._args[1:]
41
42 def get_method(self):
43 return self._method
44
45 def _raise(self, message):
46- spec = inspect.formatargspec(self._args, self._varargs,
47- self._varkwargs, self._defaults)
48+ if six.PY3:
49+ spec = str(self._signature)
50+ else:
51+ spec = inspect.formatargspec(self._args, self._varargs,
52+ self._varkwargs, self._defaults)
53 raise AssertionError("Specification is %s%s: %s" %
54 (self._method.__name__, spec, message))
55
56@@ -1866,20 +1887,26 @@
57 if self._unsupported:
58 return # Can't check it. Happens with builtin functions. :-(
59 action = path.actions[-1]
60- obtained_len = len(action.args)
61- obtained_kwargs = action.kwargs.copy()
62- nodefaults_len = len(self._args) - len(self._defaults)
63- for i, name in enumerate(self._args):
64- if i < obtained_len and name in action.kwargs:
65- self._raise("%r provided twice" % name)
66- if (i >= obtained_len and i < nodefaults_len and
67- name not in action.kwargs):
68- self._raise("%r not provided" % name)
69- obtained_kwargs.pop(name, None)
70- if obtained_len > len(self._args) and not self._varargs:
71- self._raise("too many args provided")
72- if obtained_kwargs and not self._varkwargs:
73- self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs))
74+ if six.PY3:
75+ try:
76+ self._signature.bind(*action.args, **action.kwargs)
77+ except TypeError as e:
78+ self._raise(str(e))
79+ else:
80+ obtained_len = len(action.args)
81+ obtained_kwargs = action.kwargs.copy()
82+ nodefaults_len = len(self._args) - len(self._defaults)
83+ for i, name in enumerate(self._args):
84+ if i < obtained_len and name in action.kwargs:
85+ self._raise("%r provided twice" % name)
86+ if (i >= obtained_len and i < nodefaults_len and
87+ name not in action.kwargs):
88+ self._raise("%r not provided" % name)
89+ obtained_kwargs.pop(name, None)
90+ if obtained_len > len(self._args) and not self._varargs:
91+ self._raise("too many args provided")
92+ if obtained_kwargs and not self._varkwargs:
93+ self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs))
94
95 def spec_checker_recorder(mocker, event):
96 spec = event.path.root_mock.__mocker_spec__

Subscribers

People subscribed via source and target branches

to status/vote changes: