15
15
from ._compat import _basestring
16
16
from .logger import pformat
17
17
from ._memory_helpers import open_py_source
18
+ from ._compat import PY3_OR_LATER
19
+
18
20
19
21
def get_func_code (func ):
20
22
""" Attempts to retrieve a reliable function code hash.
@@ -156,6 +158,53 @@ def get_func_name(func, resolv_alias=True, win_characters=True):
156
158
return module , name
157
159
158
160
161
+ def getfullargspec (func ):
162
+ """Compatibility function to provide inspect.getfullargspec in Python 2
163
+
164
+ This should be rewritten using a backport of Python 3 signature
165
+ once we drop support for Python 2.6. We went for a simpler
166
+ approach at the time of writing because signature uses OrderedDict
167
+ which is not available in Python 2.6.
168
+ """
169
+ try :
170
+ return inspect .getfullargspec (func )
171
+ except AttributeError :
172
+ arg_spec = inspect .getargspec (func )
173
+ import collections
174
+ tuple_fields = ('args varargs varkw defaults kwonlyargs '
175
+ 'kwonlydefaults annotations' )
176
+ tuple_type = collections .namedtuple ('FullArgSpec' , tuple_fields )
177
+
178
+ return tuple_type (args = arg_spec .args ,
179
+ varargs = arg_spec .varargs ,
180
+ varkw = arg_spec .keywords ,
181
+ defaults = arg_spec .defaults ,
182
+ kwonlyargs = [],
183
+ kwonlydefaults = None ,
184
+ annotations = {})
185
+
186
+
187
+ def _signature_str (function_name , arg_spec ):
188
+ """Helper function to output a function signature"""
189
+ # inspect.formatargspec can not deal with the same
190
+ # number of arguments in python 2 and 3
191
+ arg_spec_for_format = arg_spec [:7 if PY3_OR_LATER else 4 ]
192
+
193
+ arg_spec_str = inspect .formatargspec (* arg_spec_for_format )
194
+ return '{0}{1}' .format (function_name , arg_spec_str )
195
+
196
+
197
+ def _function_called_str (function_name , args , kwargs ):
198
+ """Helper function to output a function call"""
199
+ template_str = '{0}({1}, {2})'
200
+
201
+ args_str = repr (args )[1 :- 1 ]
202
+ kwargs_str = ', ' .join ('%s=%s' % (k , v )
203
+ for k , v in kwargs .items ())
204
+ return template_str .format (function_name , args_str ,
205
+ kwargs_str )
206
+
207
+
159
208
def filter_args (func , ignore_lst , args = (), kwargs = dict ()):
160
209
""" Filters the given args and kwargs using a list of arguments to
161
210
ignore, and a function specification.
@@ -180,24 +229,23 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
180
229
args = list (args )
181
230
if isinstance (ignore_lst , _basestring ):
182
231
# Catch a common mistake
183
- raise ValueError ('ignore_lst must be a list of parameters to ignore '
232
+ raise ValueError (
233
+ 'ignore_lst must be a list of parameters to ignore '
184
234
'%s (type %s) was given' % (ignore_lst , type (ignore_lst )))
185
235
# Special case for functools.partial objects
186
236
if (not inspect .ismethod (func ) and not inspect .isfunction (func )):
187
237
if ignore_lst :
188
238
warnings .warn ('Cannot inspect object %s, ignore list will '
189
- 'not work.' % func , stacklevel = 2 )
239
+ 'not work.' % func , stacklevel = 2 )
190
240
return {'*' : args , '**' : kwargs }
191
- arg_spec = inspect .getargspec (func )
192
- # We need to if/them to account for different versions of Python
193
- if hasattr (arg_spec , 'args' ):
194
- arg_names = arg_spec .args
195
- arg_defaults = arg_spec .defaults
196
- arg_keywords = arg_spec .keywords
197
- arg_varargs = arg_spec .varargs
198
- else :
199
- arg_names , arg_varargs , arg_keywords , arg_defaults = arg_spec
200
- arg_defaults = arg_defaults or {}
241
+ arg_spec = getfullargspec (func )
242
+ arg_names = arg_spec .args + arg_spec .kwonlyargs
243
+ arg_defaults = arg_spec .defaults or ()
244
+ arg_defaults = arg_defaults + tuple (arg_spec .kwonlydefaults [k ]
245
+ for k in arg_spec .kwonlyargs )
246
+ arg_varargs = arg_spec .varargs
247
+ arg_varkw = arg_spec .varkw
248
+
201
249
if inspect .ismethod (func ):
202
250
# First argument is 'self', it has been removed by Python
203
251
# we need to add it back:
@@ -211,7 +259,18 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
211
259
for arg_position , arg_name in enumerate (arg_names ):
212
260
if arg_position < len (args ):
213
261
# Positional argument or keyword argument given as positional
214
- arg_dict [arg_name ] = args [arg_position ]
262
+ if arg_name not in arg_spec .kwonlyargs :
263
+ arg_dict [arg_name ] = args [arg_position ]
264
+ else :
265
+ raise ValueError (
266
+ "Keyword-only parameter '%s' was passed as "
267
+ 'positional parameter for %s:\n '
268
+ ' %s was called.'
269
+ % (arg_name ,
270
+ _signature_str (name , arg_spec ),
271
+ _function_called_str (name , args , kwargs ))
272
+ )
273
+
215
274
else :
216
275
position = arg_position - len (arg_names )
217
276
if arg_name in kwargs :
@@ -221,28 +280,24 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
221
280
arg_dict [arg_name ] = arg_defaults [position ]
222
281
except (IndexError , KeyError ):
223
282
# Missing argument
224
- raise ValueError ('Wrong number of arguments for %s%s:\n '
225
- ' %s(%s, %s) was called.'
226
- % (name ,
227
- inspect .formatargspec (* inspect .getargspec (func )),
228
- name ,
229
- repr (args )[1 :- 1 ],
230
- ', ' .join ('%s=%s' % (k , v )
231
- for k , v in kwargs .items ())
232
- )
233
- )
283
+ raise ValueError (
284
+ 'Wrong number of arguments for %s:\n '
285
+ ' %s was called.'
286
+ % (_signature_str (name , arg_spec ),
287
+ _function_called_str (name , args , kwargs ))
288
+ )
234
289
235
290
varkwargs = dict ()
236
291
for arg_name , arg_value in sorted (kwargs .items ()):
237
292
if arg_name in arg_dict :
238
293
arg_dict [arg_name ] = arg_value
239
- elif arg_keywords is not None :
294
+ elif arg_varkw is not None :
240
295
varkwargs [arg_name ] = arg_value
241
296
else :
242
297
raise TypeError ("Ignore list for %s() contains an unexpected "
243
298
"keyword argument '%s'" % (name , arg_name ))
244
299
245
- if arg_keywords is not None :
300
+ if arg_varkw is not None :
246
301
arg_dict ['**' ] = varkwargs
247
302
if arg_varargs is not None :
248
303
varargs = args [arg_position + 1 :]
@@ -254,13 +309,10 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
254
309
arg_dict .pop (item )
255
310
else :
256
311
raise ValueError ("Ignore list: argument '%s' is not defined for "
257
- "function %s%s" %
258
- (item , name ,
259
- inspect .formatargspec (arg_names ,
260
- arg_varargs ,
261
- arg_keywords ,
262
- arg_defaults ,
263
- )))
312
+ "function %s"
313
+ % (item ,
314
+ _signature_str (name , arg_spec ))
315
+ )
264
316
# XXX: Return a sorted list of pairs?
265
317
return arg_dict
266
318
0 commit comments