1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Common utility library."""
19
20 __author__ = ['rafek@google.com (Rafe Kaplan)',
21 'guido@google.com (Guido van Rossum)',
22 ]
23 __all__ = [
24 'positional',
25 ]
26
27 import gflags
28 import inspect
29 import logging
30
31 logger = logging.getLogger(__name__)
32
33 FLAGS = gflags.FLAGS
34
35 gflags.DEFINE_enum('positional_parameters_enforcement', 'WARNING',
36 ['EXCEPTION', 'WARNING', 'IGNORE'],
37 'The action when an oauth2client.util.positional declaration is violated.')
38
39
41 """A decorator to declare that only the first N arguments my be positional.
42
43 This decorator makes it easy to support Python 3 style key-word only
44 parameters. For example, in Python 3 it is possible to write:
45
46 def fn(pos1, *, kwonly1=None, kwonly1=None):
47 ...
48
49 All named parameters after * must be a keyword:
50
51 fn(10, 'kw1', 'kw2') # Raises exception.
52 fn(10, kwonly1='kw1') # Ok.
53
54 Example:
55 To define a function like above, do:
56
57 @positional(1)
58 def fn(pos1, kwonly1=None, kwonly2=None):
59 ...
60
61 If no default value is provided to a keyword argument, it becomes a required
62 keyword argument:
63
64 @positional(0)
65 def fn(required_kw):
66 ...
67
68 This must be called with the keyword parameter:
69
70 fn() # Raises exception.
71 fn(10) # Raises exception.
72 fn(required_kw=10) # Ok.
73
74 When defining instance or class methods always remember to account for
75 'self' and 'cls':
76
77 class MyClass(object):
78
79 @positional(2)
80 def my_method(self, pos1, kwonly1=None):
81 ...
82
83 @classmethod
84 @positional(2)
85 def my_method(cls, pos1, kwonly1=None):
86 ...
87
88 The positional decorator behavior is controlled by the
89 --positional_parameters_enforcement flag. The flag may be set to 'EXCEPTION',
90 'WARNING' or 'IGNORE' to raise an exception, log a warning, or do nothing,
91 respectively, if a declaration is violated.
92
93 Args:
94 max_positional_arguments: Maximum number of positional arguments. All
95 parameters after the this index must be keyword only.
96
97 Returns:
98 A decorator that prevents using arguments after max_positional_args from
99 being used as positional parameters.
100
101 Raises:
102 TypeError if a key-word only argument is provided as a positional parameter,
103 but only if the --positional_parameters_enforcement flag is set to
104 'EXCEPTION'.
105 """
106 def positional_decorator(wrapped):
107 def positional_wrapper(*args, **kwargs):
108 if len(args) > max_positional_args:
109 plural_s = ''
110 if max_positional_args != 1:
111 plural_s = 's'
112 message = '%s() takes at most %d positional argument%s (%d given)' % (
113 wrapped.__name__, max_positional_args, plural_s, len(args))
114 if FLAGS.positional_parameters_enforcement == 'EXCEPTION':
115 raise TypeError(message)
116 elif FLAGS.positional_parameters_enforcement == 'WARNING':
117 logger.warning(message)
118 else:
119 pass
120 return wrapped(*args, **kwargs)
121 return positional_wrapper
122
123 if isinstance(max_positional_args, (int, long)):
124 return positional_decorator
125 else:
126 args, _, _, defaults = inspect.getargspec(max_positional_args)
127 return positional(len(args) - len(defaults))(max_positional_args)
128