blob: bda14c678f3774319493d470989bcc8b72398e7d [file] [log] [blame]
Joe Gregorio68a8cfe2012-08-03 16:17:40 -04001#!/usr/bin/env python
2#
3# Copyright 2010 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
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
27import gflags
28import inspect
29import logging
30
31logger = logging.getLogger(__name__)
32
33FLAGS = gflags.FLAGS
34
35gflags.DEFINE_enum('positional_parameters_enforcement', 'WARNING',
36 ['EXCEPTION', 'WARNING', 'IGNORE'],
37 'The action when an oauth2client.util.positional declaration is violated.')
38
39
40def positional(max_positional_args):
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: # IGNORE
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)