1"""Internal module for better introspection of builtins.
2
3The main functions are ``is_builtin_valid_args``, ``is_builtin_partial_args``,
4and ``has_unknown_args``. Other functions in this module support these three.
5
6Notably, we create a ``signatures`` registry to enable introspection of
7builtin functions in any Python version. This includes builtins that
8have more than one valid signature. Currently, the registry includes
9builtins from ``builtins``, ``functools``, ``itertools``, and ``operator``
10modules. More can be added as requested. We don't guarantee full coverage.
11
12Everything in this module should be regarded as implementation details.
13Users should try to not use this module directly.
14"""
15import functools
16import inspect
17import itertools
18import operator
19from importlib import import_module
20
21from .functoolz import (is_partial_args, is_arity, has_varargs,
22 has_keywords, num_required_args)
23
24import builtins
25
26# We mock builtin callables using lists of tuples with lambda functions.
27#
28# The tuple spec is (num_position_args, lambda_func, keyword_only_args).
29#
30# num_position_args:
31# - The number of positional-only arguments. If not specified,
32# all positional arguments are considered positional-only.
33#
34# lambda_func:
35# - lambda function that matches a signature of a builtin, but does
36# not include keyword-only arguments.
37#
38# keyword_only_args: (optional)
39# - Tuple of keyword-only arguments.
40
41module_info = {}
42
43module_info[builtins] = dict(
44 abs=[
45 lambda x: None],
46 all=[
47 lambda iterable: None],
48 anext=[
49 lambda aiterator: None,
50 lambda aiterator, default: None],
51 any=[
52 lambda iterable: None],
53 apply=[
54 lambda object: None,
55 lambda object, args: None,
56 lambda object, args, kwargs: None],
57 ascii=[
58 lambda obj: None],
59 bin=[
60 lambda number: None],
61 bool=[
62 lambda x=False: None],
63 buffer=[
64 lambda object: None,
65 lambda object, offset: None,
66 lambda object, offset, size: None],
67 bytearray=[
68 lambda: None,
69 lambda int: None,
70 lambda string, encoding='utf8', errors='strict': None],
71 callable=[
72 lambda obj: None],
73 chr=[
74 lambda i: None],
75 classmethod=[
76 lambda function: None],
77 cmp=[
78 lambda x, y: None],
79 coerce=[
80 lambda x, y: None],
81 complex=[
82 lambda real=0, imag=0: None],
83 delattr=[
84 lambda obj, name: None],
85 dict=[
86 lambda **kwargs: None,
87 lambda mapping, **kwargs: None],
88 dir=[
89 lambda: None,
90 lambda object: None],
91 divmod=[
92 lambda x, y: None],
93 enumerate=[
94 (0, lambda iterable, start=0: None)],
95 eval=[
96 lambda source: None,
97 lambda source, globals: None,
98 lambda source, globals, locals: None],
99 execfile=[
100 lambda filename: None,
101 lambda filename, globals: None,
102 lambda filename, globals, locals: None],
103 file=[
104 (0, lambda name, mode='r', buffering=-1: None)],
105 filter=[
106 lambda function, iterable: None],
107 float=[
108 lambda x=0.0: None],
109 format=[
110 lambda value: None,
111 lambda value, format_spec: None],
112 frozenset=[
113 lambda: None,
114 lambda iterable: None],
115 getattr=[
116 lambda object, name: None,
117 lambda object, name, default: None],
118 globals=[
119 lambda: None],
120 hasattr=[
121 lambda obj, name: None],
122 hash=[
123 lambda obj: None],
124 hex=[
125 lambda number: None],
126 id=[
127 lambda obj: None],
128 input=[
129 lambda: None,
130 lambda prompt: None],
131 int=[
132 lambda x=0: None,
133 (0, lambda x, base=10: None)],
134 intern=[
135 lambda string: None],
136 isinstance=[
137 lambda obj, class_or_tuple: None],
138 issubclass=[
139 lambda cls, class_or_tuple: None],
140 iter=[
141 lambda iterable: None,
142 lambda callable, sentinel: None],
143 len=[
144 lambda obj: None],
145 list=[
146 lambda: None,
147 lambda iterable: None],
148 locals=[
149 lambda: None],
150 long=[
151 lambda x=0: None,
152 (0, lambda x, base=10: None)],
153 map=[
154 lambda func, sequence, *iterables: None],
155 memoryview=[
156 (0, lambda object: None)],
157 next=[
158 lambda iterator: None,
159 lambda iterator, default: None],
160 object=[
161 lambda: None],
162 oct=[
163 lambda number: None],
164 ord=[
165 lambda c: None],
166 pow=[
167 lambda x, y: None,
168 lambda x, y, z: None],
169 property=[
170 lambda fget=None, fset=None, fdel=None, doc=None: None],
171 range=[
172 lambda stop: None,
173 lambda start, stop: None,
174 lambda start, stop, step: None],
175 raw_input=[
176 lambda: None,
177 lambda prompt: None],
178 reduce=[
179 lambda function, sequence: None,
180 lambda function, sequence, initial: None],
181 reload=[
182 lambda module: None],
183 repr=[
184 lambda obj: None],
185 reversed=[
186 lambda sequence: None],
187 round=[
188 (0, lambda number, ndigits=0: None)],
189 set=[
190 lambda: None,
191 lambda iterable: None],
192 setattr=[
193 lambda obj, name, value: None],
194 slice=[
195 lambda stop: None,
196 lambda start, stop: None,
197 lambda start, stop, step: None],
198 staticmethod=[
199 lambda function: None],
200 sum=[
201 lambda iterable: None,
202 lambda iterable, start: None],
203 super=[
204 lambda type: None,
205 lambda type, obj: None],
206 tuple=[
207 lambda: None,
208 lambda iterable: None],
209 type=[
210 lambda object: None,
211 lambda name, bases, dict: None],
212 unichr=[
213 lambda i: None],
214 unicode=[
215 lambda object: None,
216 lambda string='', encoding='utf8', errors='strict': None],
217 vars=[
218 lambda: None,
219 lambda object: None],
220 xrange=[
221 lambda stop: None,
222 lambda start, stop: None,
223 lambda start, stop, step: None],
224 zip=[
225 lambda *iterables: None],
226 __build_class__=[
227 (2, lambda func, name, *bases, **kwds: None, ('metaclass',))],
228 __import__=[
229 (0, lambda name, globals=None, locals=None, fromlist=None,
230 level=None: None)],
231)
232module_info[builtins]['exec'] = [
233 lambda source: None,
234 lambda source, globals: None,
235 lambda source, globals, locals: None]
236
237module_info[builtins].update(
238 breakpoint=[
239 lambda *args, **kws: None],
240 bytes=[
241 lambda: None,
242 lambda int: None,
243 lambda string, encoding='utf8', errors='strict': None],
244 compile=[
245 (0, lambda source, filename, mode, flags=0,
246 dont_inherit=False, optimize=-1: None)],
247 max=[
248 (1, lambda iterable: None, ('default', 'key',)),
249 (1, lambda arg1, arg2, *args: None, ('key',))],
250 min=[
251 (1, lambda iterable: None, ('default', 'key',)),
252 (1, lambda arg1, arg2, *args: None, ('key',))],
253 open=[
254 (0, lambda file, mode='r', buffering=-1, encoding=None,
255 errors=None, newline=None, closefd=True, opener=None: None)],
256 sorted=[
257 (1, lambda iterable: None, ('key', 'reverse'))],
258 str=[
259 lambda object='', encoding='utf', errors='strict': None],
260)
261module_info[builtins]['print'] = [
262 (0, lambda *args: None, ('sep', 'end', 'file', 'flush',))]
263
264
265module_info[functools] = dict(
266 cmp_to_key=[
267 (0, lambda mycmp: None)],
268 partial=[
269 lambda func, *args, **kwargs: None],
270 partialmethod=[
271 lambda func, *args, **kwargs: None],
272 reduce=[
273 lambda function, sequence: None,
274 lambda function, sequence, initial: None],
275)
276
277module_info[itertools] = dict(
278 accumulate=[
279 (0, lambda iterable, func=None: None)],
280 chain=[
281 lambda *iterables: None],
282 combinations=[
283 (0, lambda iterable, r: None)],
284 combinations_with_replacement=[
285 (0, lambda iterable, r: None)],
286 compress=[
287 (0, lambda data, selectors: None)],
288 count=[
289 lambda start=0, step=1: None],
290 cycle=[
291 lambda iterable: None],
292 dropwhile=[
293 lambda predicate, iterable: None],
294 filterfalse=[
295 lambda function, sequence: None],
296 groupby=[
297 (0, lambda iterable, key=None: None)],
298 ifilter=[
299 lambda function, sequence: None],
300 ifilterfalse=[
301 lambda function, sequence: None],
302 imap=[
303 lambda func, sequence, *iterables: None],
304 islice=[
305 lambda iterable, stop: None,
306 lambda iterable, start, stop: None,
307 lambda iterable, start, stop, step: None],
308 izip=[
309 lambda *iterables: None],
310 izip_longest=[
311 (0, lambda *iterables: None, ('fillvalue',))],
312 pairwise=[
313 lambda iterable: None],
314 permutations=[
315 (0, lambda iterable, r=0: None)],
316 product=[
317 (0, lambda *iterables: None, ('repeat',))],
318 repeat=[
319 (0, lambda object, times=0: None)],
320 starmap=[
321 lambda function, sequence: None],
322 takewhile=[
323 lambda predicate, iterable: None],
324 tee=[
325 lambda iterable: None,
326 lambda iterable, n: None],
327 zip_longest=[
328 (0, lambda *iterables: None, ('fillvalue',))],
329)
330
331
332module_info[operator] = dict(
333 __abs__=[
334 lambda a: None],
335 __add__=[
336 lambda a, b: None],
337 __and__=[
338 lambda a, b: None],
339 __concat__=[
340 lambda a, b: None],
341 __contains__=[
342 lambda a, b: None],
343 __delitem__=[
344 lambda a, b: None],
345 __delslice__=[
346 lambda a, b, c: None],
347 __div__=[
348 lambda a, b: None],
349 __eq__=[
350 lambda a, b: None],
351 __floordiv__=[
352 lambda a, b: None],
353 __ge__=[
354 lambda a, b: None],
355 __getitem__=[
356 lambda a, b: None],
357 __getslice__=[
358 lambda a, b, c: None],
359 __gt__=[
360 lambda a, b: None],
361 __iadd__=[
362 lambda a, b: None],
363 __iand__=[
364 lambda a, b: None],
365 __iconcat__=[
366 lambda a, b: None],
367 __idiv__=[
368 lambda a, b: None],
369 __ifloordiv__=[
370 lambda a, b: None],
371 __ilshift__=[
372 lambda a, b: None],
373 __imatmul__=[
374 lambda a, b: None],
375 __imod__=[
376 lambda a, b: None],
377 __imul__=[
378 lambda a, b: None],
379 __index__=[
380 lambda a: None],
381 __inv__=[
382 lambda a: None],
383 __invert__=[
384 lambda a: None],
385 __ior__=[
386 lambda a, b: None],
387 __ipow__=[
388 lambda a, b: None],
389 __irepeat__=[
390 lambda a, b: None],
391 __irshift__=[
392 lambda a, b: None],
393 __isub__=[
394 lambda a, b: None],
395 __itruediv__=[
396 lambda a, b: None],
397 __ixor__=[
398 lambda a, b: None],
399 __le__=[
400 lambda a, b: None],
401 __lshift__=[
402 lambda a, b: None],
403 __lt__=[
404 lambda a, b: None],
405 __matmul__=[
406 lambda a, b: None],
407 __mod__=[
408 lambda a, b: None],
409 __mul__=[
410 lambda a, b: None],
411 __ne__=[
412 lambda a, b: None],
413 __neg__=[
414 lambda a: None],
415 __not__=[
416 lambda a: None],
417 __or__=[
418 lambda a, b: None],
419 __pos__=[
420 lambda a: None],
421 __pow__=[
422 lambda a, b: None],
423 __repeat__=[
424 lambda a, b: None],
425 __rshift__=[
426 lambda a, b: None],
427 __setitem__=[
428 lambda a, b, c: None],
429 __setslice__=[
430 lambda a, b, c, d: None],
431 __sub__=[
432 lambda a, b: None],
433 __truediv__=[
434 lambda a, b: None],
435 __xor__=[
436 lambda a, b: None],
437 _abs=[
438 lambda x: None],
439 _compare_digest=[
440 lambda a, b: None],
441 abs=[
442 lambda a: None],
443 add=[
444 lambda a, b: None],
445 and_=[
446 lambda a, b: None],
447 attrgetter=[
448 lambda attr, *args: None],
449 concat=[
450 lambda a, b: None],
451 contains=[
452 lambda a, b: None],
453 countOf=[
454 lambda a, b: None],
455 delitem=[
456 lambda a, b: None],
457 delslice=[
458 lambda a, b, c: None],
459 div=[
460 lambda a, b: None],
461 eq=[
462 lambda a, b: None],
463 floordiv=[
464 lambda a, b: None],
465 ge=[
466 lambda a, b: None],
467 getitem=[
468 lambda a, b: None],
469 getslice=[
470 lambda a, b, c: None],
471 gt=[
472 lambda a, b: None],
473 iadd=[
474 lambda a, b: None],
475 iand=[
476 lambda a, b: None],
477 iconcat=[
478 lambda a, b: None],
479 idiv=[
480 lambda a, b: None],
481 ifloordiv=[
482 lambda a, b: None],
483 ilshift=[
484 lambda a, b: None],
485 imatmul=[
486 lambda a, b: None],
487 imod=[
488 lambda a, b: None],
489 imul=[
490 lambda a, b: None],
491 index=[
492 lambda a: None],
493 indexOf=[
494 lambda a, b: None],
495 inv=[
496 lambda a: None],
497 invert=[
498 lambda a: None],
499 ior=[
500 lambda a, b: None],
501 ipow=[
502 lambda a, b: None],
503 irepeat=[
504 lambda a, b: None],
505 irshift=[
506 lambda a, b: None],
507 is_=[
508 lambda a, b: None],
509 is_not=[
510 lambda a, b: None],
511 isCallable=[
512 lambda a: None],
513 isMappingType=[
514 lambda a: None],
515 isNumberType=[
516 lambda a: None],
517 isSequenceType=[
518 lambda a: None],
519 isub=[
520 lambda a, b: None],
521 itemgetter=[
522 lambda item, *args: None],
523 itruediv=[
524 lambda a, b: None],
525 ixor=[
526 lambda a, b: None],
527 le=[
528 lambda a, b: None],
529 length_hint=[
530 lambda obj: None,
531 lambda obj, default: None],
532 lshift=[
533 lambda a, b: None],
534 lt=[
535 lambda a, b: None],
536 matmul=[
537 lambda a, b: None],
538 methodcaller=[
539 lambda name, *args, **kwargs: None],
540 mod=[
541 lambda a, b: None],
542 mul=[
543 lambda a, b: None],
544 ne=[
545 lambda a, b: None],
546 neg=[
547 lambda a: None],
548 not_=[
549 lambda a: None],
550 or_=[
551 lambda a, b: None],
552 pos=[
553 lambda a: None],
554 pow=[
555 lambda a, b: None],
556 repeat=[
557 lambda a, b: None],
558 rshift=[
559 lambda a, b: None],
560 sequenceIncludes=[
561 lambda a, b: None],
562 setitem=[
563 lambda a, b, c: None],
564 setslice=[
565 lambda a, b, c, d: None],
566 sub=[
567 lambda a, b: None],
568 truediv=[
569 lambda a, b: None],
570 truth=[
571 lambda a: None],
572 xor=[
573 lambda a, b: None],
574)
575
576module_info['toolz'] = dict(
577 curry=[
578 (0, lambda *args, **kwargs: None)],
579 excepts=[
580 (0, lambda exc, func, handler=None: None)],
581 flip=[
582 (0, lambda func=None, a=None, b=None: None)],
583 juxt=[
584 (0, lambda *funcs: None)],
585 memoize=[
586 (0, lambda func=None, cache=None, key=None: None)],
587)
588
589module_info['toolz.functoolz'] = dict(
590 Compose=[
591 (0, lambda funcs: None)],
592 InstanceProperty=[
593 (0, lambda fget=None, fset=None, fdel=None, doc=None,
594 classval=None: None)],
595)
596
597
598def num_pos_args(sigspec):
599 """ Return the number of positional arguments. ``f(x, y=1)`` has 1"""
600 return sum(1 for x in sigspec.parameters.values()
601 if x.kind == x.POSITIONAL_OR_KEYWORD
602 and x.default is x.empty)
603
604
605def get_exclude_keywords(num_pos_only, sigspec):
606 """ Return the names of position-only arguments if func has **kwargs"""
607 if num_pos_only == 0:
608 return ()
609 has_kwargs = any(x.kind == x.VAR_KEYWORD
610 for x in sigspec.parameters.values())
611 if not has_kwargs:
612 return ()
613 pos_args = list(sigspec.parameters.values())[:num_pos_only]
614 return tuple(x.name for x in pos_args)
615
616
617def signature_or_spec(func):
618 try:
619 return inspect.signature(func)
620 except (ValueError, TypeError):
621 return None
622
623
624def expand_sig(sig):
625 """ Convert the signature spec in ``module_info`` to add to ``signatures``
626
627 The input signature spec is one of:
628 - ``lambda_func``
629 - ``(num_position_args, lambda_func)``
630 - ``(num_position_args, lambda_func, keyword_only_args)``
631
632 The output signature spec is:
633 ``(num_position_args, lambda_func, keyword_exclude, sigspec)``
634
635 where ``keyword_exclude`` includes keyword only arguments and, if variadic
636 keywords is present, the names of position-only argument. The latter is
637 included to support builtins such as ``partial(func, *args, **kwargs)``,
638 which allows ``func=`` to be used as a keyword even though it's the name
639 of a positional argument.
640 """
641 if isinstance(sig, tuple):
642 if len(sig) == 3:
643 num_pos_only, func, keyword_only = sig
644 assert isinstance(sig[-1], tuple)
645 else:
646 num_pos_only, func = sig
647 keyword_only = ()
648 sigspec = signature_or_spec(func)
649 else:
650 func = sig
651 sigspec = signature_or_spec(func)
652 num_pos_only = num_pos_args(sigspec)
653 keyword_only = ()
654 keyword_exclude = get_exclude_keywords(num_pos_only, sigspec)
655 return num_pos_only, func, keyword_only + keyword_exclude, sigspec
656
657
658signatures = {}
659
660
661def create_signature_registry(module_info=module_info, signatures=signatures):
662 for module, info in module_info.items():
663 if isinstance(module, str):
664 module = import_module(module)
665 for name, sigs in info.items():
666 if hasattr(module, name):
667 new_sigs = tuple(expand_sig(sig) for sig in sigs)
668 signatures[getattr(module, name)] = new_sigs
669
670
671def check_valid(sig, args, kwargs):
672 """ Like ``is_valid_args`` for the given signature spec"""
673 num_pos_only, func, keyword_exclude, sigspec = sig
674 if len(args) < num_pos_only:
675 return False
676 if keyword_exclude:
677 kwargs = dict(kwargs)
678 for item in keyword_exclude:
679 kwargs.pop(item, None)
680 try:
681 func(*args, **kwargs)
682 return True
683 except TypeError:
684 return False
685
686
687def _is_valid_args(func, args, kwargs):
688 """ Like ``is_valid_args`` for builtins in our ``signatures`` registry"""
689 if func not in signatures:
690 return None
691 sigs = signatures[func]
692 return any(check_valid(sig, args, kwargs) for sig in sigs)
693
694
695def check_partial(sig, args, kwargs):
696 """ Like ``is_partial_args`` for the given signature spec"""
697 num_pos_only, func, keyword_exclude, sigspec = sig
698 if len(args) < num_pos_only:
699 pad = (None,) * (num_pos_only - len(args))
700 args = args + pad
701 if keyword_exclude:
702 kwargs = dict(kwargs)
703 for item in keyword_exclude:
704 kwargs.pop(item, None)
705 return is_partial_args(func, args, kwargs, sigspec)
706
707
708def _is_partial_args(func, args, kwargs):
709 """ Like ``is_partial_args`` for builtins in our ``signatures`` registry"""
710 if func not in signatures:
711 return None
712 sigs = signatures[func]
713 return any(check_partial(sig, args, kwargs) for sig in sigs)
714
715
716def check_arity(n, sig):
717 num_pos_only, func, keyword_exclude, sigspec = sig
718 if keyword_exclude or num_pos_only > n:
719 return False
720 return is_arity(n, func, sigspec)
721
722
723def _is_arity(n, func):
724 if func not in signatures:
725 return None
726 sigs = signatures[func]
727 checks = [check_arity(n, sig) for sig in sigs]
728 if all(checks):
729 return True
730 elif any(checks):
731 return None
732 return False
733
734
735def check_varargs(sig):
736 num_pos_only, func, keyword_exclude, sigspec = sig
737 return has_varargs(func, sigspec)
738
739
740def _has_varargs(func):
741 if func not in signatures:
742 return None
743 sigs = signatures[func]
744 checks = [check_varargs(sig) for sig in sigs]
745 if all(checks):
746 return True
747 elif any(checks):
748 return None
749 return False
750
751
752def check_keywords(sig):
753 num_pos_only, func, keyword_exclude, sigspec = sig
754 if keyword_exclude:
755 return True
756 return has_keywords(func, sigspec)
757
758
759def _has_keywords(func):
760 if func not in signatures:
761 return None
762 sigs = signatures[func]
763 checks = [check_keywords(sig) for sig in sigs]
764 if all(checks):
765 return True
766 elif any(checks):
767 return None
768 return False
769
770
771def check_required_args(sig):
772 num_pos_only, func, keyword_exclude, sigspec = sig
773 return num_required_args(func, sigspec)
774
775
776def _num_required_args(func):
777 if func not in signatures:
778 return None
779 sigs = signatures[func]
780 vals = [check_required_args(sig) for sig in sigs]
781 val = vals[0]
782 if all(x == val for x in vals):
783 return val
784 return None