1""" 2Copy/paste from scrapy source at the moment, to ensure tests are working. 3Refactoring to come later 4""" 5import inspect 6from functools import partial 7 8from itemadapter import is_item 9 10 11_ITERABLE_SINGLE_VALUES = str, bytes 12 13 14def arg_to_iter(arg): 15 """Convert an argument to an iterable. The argument can be a None, single 16 value, or an iterable. 17 18 Exception: if arg is a dict, [arg] will be returned 19 """ 20 if arg is None: 21 return [] 22 elif ( 23 hasattr(arg, '__iter__') 24 and not isinstance(arg, _ITERABLE_SINGLE_VALUES) 25 and not is_item(arg) 26 ): 27 return arg 28 else: 29 return [arg] 30 31 32def get_func_args(func, stripself=False): 33 """Return the argument name list of a callable""" 34 if inspect.isfunction(func): 35 func_args, _, _, _ = _getargspec_py23(func) 36 elif inspect.isclass(func): 37 return get_func_args(func.__init__, True) 38 elif inspect.ismethod(func): 39 return get_func_args(func.__func__, True) 40 elif inspect.ismethoddescriptor(func): 41 return [] 42 elif isinstance(func, partial): 43 return [x for x in get_func_args(func.func)[len(func.args):] 44 if not (func.keywords and x in func.keywords)] 45 elif hasattr(func, '__call__'): 46 if inspect.isroutine(func): 47 return [] 48 elif getattr(func, '__name__', None) == '__call__': 49 return [] 50 else: 51 return get_func_args(func.__call__, True) 52 else: 53 raise TypeError('%s is not callable' % type(func)) 54 if stripself: 55 func_args.pop(0) 56 return func_args 57 58 59def _getargspec_py23(func): 60 """_getargspec_py23(function) -> named tuple ArgSpec(args, varargs, keywords, 61 defaults) 62 63 Was identical to inspect.getargspec() in python2, but uses 64 inspect.getfullargspec() for python3 behind the scenes to avoid 65 DeprecationWarning. 66 67 >>> def f(a, b=2, *ar, **kw): 68 ... pass 69 70 >>> _getargspec_py23(f) 71 ArgSpec(args=['a', 'b'], varargs='ar', keywords='kw', defaults=(2,)) 72 """ 73 return inspect.ArgSpec(*inspect.getfullargspec(func)[:4]) 74