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

Proposed by Colin Watson
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 (community) Approve
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.
Revision history for this message
Simon Poirier (simpoir) wrote :

+1 Not the prettiest change, but seems ok.

review: Approve
513. By Colin Watson

Explain positional-only test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'storm/tests/mocker.py'
--- storm/tests/mocker.py 2019-08-21 09:40:30 +0000
+++ storm/tests/mocker.py 2019-08-21 11:14:10 +0000
@@ -1830,22 +1830,43 @@
18301830
1831 if method:1831 if method:
1832 try:1832 try:
1833 self._args, self._varargs, self._varkwargs, self._defaults = \1833 if six.PY3:
1834 inspect.getargspec(method)1834 # On Python 3, inspect.getargspec includes the bound
1835 # first argument (self or similar) for bound methods,
1836 # which confuses matters. The modern signature API
1837 # doesn't have this problem.
1838 self._signature = inspect.signature(method)
1839 # Method descriptors don't have the first argument
1840 # already bound, but we want to skip it anyway.
1841 if getattr(method, "__objclass__", None) is not None:
1842 parameters = list(self._signature.parameters.values())
1843 # This is positional-only for unbound methods that
1844 # are implemented in C.
1845 if (parameters[0].kind ==
1846 inspect.Parameter.POSITIONAL_ONLY):
1847 self._signature = self._signature.replace(
1848 parameters=parameters[1:])
1849 else:
1850 (self._args, self._varargs, self._varkwargs,
1851 self._defaults) = inspect.getargspec(method)
1835 except TypeError:1852 except TypeError:
1836 self._unsupported = True1853 self._unsupported = True
1837 else:1854 else:
1838 if self._defaults is None:1855 if not six.PY3:
1839 self._defaults = ()1856 if self._defaults is None:
1840 if type(method) is type(self.run):1857 self._defaults = ()
1841 self._args = self._args[1:]1858 if type(method) is type(self.run):
1859 self._args = self._args[1:]
18421860
1843 def get_method(self):1861 def get_method(self):
1844 return self._method1862 return self._method
18451863
1846 def _raise(self, message):1864 def _raise(self, message):
1847 spec = inspect.formatargspec(self._args, self._varargs,1865 if six.PY3:
1848 self._varkwargs, self._defaults)1866 spec = str(self._signature)
1867 else:
1868 spec = inspect.formatargspec(self._args, self._varargs,
1869 self._varkwargs, self._defaults)
1849 raise AssertionError("Specification is %s%s: %s" %1870 raise AssertionError("Specification is %s%s: %s" %
1850 (self._method.__name__, spec, message))1871 (self._method.__name__, spec, message))
18511872
@@ -1866,20 +1887,26 @@
1866 if self._unsupported:1887 if self._unsupported:
1867 return # Can't check it. Happens with builtin functions. :-(1888 return # Can't check it. Happens with builtin functions. :-(
1868 action = path.actions[-1]1889 action = path.actions[-1]
1869 obtained_len = len(action.args)1890 if six.PY3:
1870 obtained_kwargs = action.kwargs.copy()1891 try:
1871 nodefaults_len = len(self._args) - len(self._defaults)1892 self._signature.bind(*action.args, **action.kwargs)
1872 for i, name in enumerate(self._args):1893 except TypeError as e:
1873 if i < obtained_len and name in action.kwargs:1894 self._raise(str(e))
1874 self._raise("%r provided twice" % name)1895 else:
1875 if (i >= obtained_len and i < nodefaults_len and1896 obtained_len = len(action.args)
1876 name not in action.kwargs):1897 obtained_kwargs = action.kwargs.copy()
1877 self._raise("%r not provided" % name)1898 nodefaults_len = len(self._args) - len(self._defaults)
1878 obtained_kwargs.pop(name, None)1899 for i, name in enumerate(self._args):
1879 if obtained_len > len(self._args) and not self._varargs:1900 if i < obtained_len and name in action.kwargs:
1880 self._raise("too many args provided")1901 self._raise("%r provided twice" % name)
1881 if obtained_kwargs and not self._varkwargs:1902 if (i >= obtained_len and i < nodefaults_len and
1882 self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs))1903 name not in action.kwargs):
1904 self._raise("%r not provided" % name)
1905 obtained_kwargs.pop(name, None)
1906 if obtained_len > len(self._args) and not self._varargs:
1907 self._raise("too many args provided")
1908 if obtained_kwargs and not self._varkwargs:
1909 self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs))
18831910
1884def spec_checker_recorder(mocker, event):1911def spec_checker_recorder(mocker, event):
1885 spec = event.path.root_mock.__mocker_spec__1912 spec = event.path.root_mock.__mocker_spec__

Subscribers

People subscribed via source and target branches

to status/vote changes: