|
11 | 11 | from .parser import Argument, translate_underscores
|
12 | 12 | from .util import six
|
13 | 13 |
|
14 |
| -if six.PY3: |
15 |
| - from itertools import zip_longest |
16 |
| -else: |
17 |
| - from itertools import izip_longest as zip_longest |
18 |
| - |
19 |
| - |
20 |
| -#: Sentinel object representing a truly blank value (vs ``None``). |
21 |
| -NO_DEFAULT = object() |
22 |
| - |
23 | 14 |
|
24 | 15 | class Task(object):
|
25 | 16 | """
|
@@ -134,45 +125,40 @@ def called(self):
|
134 | 125 |
|
135 | 126 | def argspec(self, body):
|
136 | 127 | """
|
137 |
| - Returns two-tuple: |
| 128 | + Returns a modified `inspect.Signature` based on that of ``body``. |
138 | 129 |
|
139 |
| - * First item is list of arg names, in order defined. |
140 |
| -
|
141 |
| - * I.e. we *cannot* simply use a dict's ``keys()`` method here. |
142 |
| -
|
143 |
| - * Second item is dict mapping arg names to default values or |
144 |
| - `.NO_DEFAULT` (an 'empty' value distinct from None, since None |
145 |
| - is a valid value on its own). |
| 130 | + :returns: |
| 131 | + an `inspect.Signature` matching that of ``body``, but with the |
| 132 | + initial context argument removed. |
| 133 | + :raises TypeError: |
| 134 | + if the task lacks an initial positional `.Context` argument. |
146 | 135 |
|
147 | 136 | .. versionadded:: 1.0
|
| 137 | + .. versionchanged:: 2.0 |
| 138 | + Changed from returning a two-tuple of ``(arg_names, spec_dict)`` to |
| 139 | + returning an `inspect.Signature`. |
148 | 140 | """
|
149 | 141 | # Handle callable-but-not-function objects
|
150 |
| - # TODO: __call__ exhibits the 'self' arg; do we manually nix 1st result |
151 |
| - # in argspec, or is there a way to get the "really callable" spec? |
152 | 142 | func = body if isinstance(body, types.FunctionType) else body.__call__
|
153 |
| - spec = inspect.getargspec(func) |
154 |
| - arg_names = spec.args[:] |
155 |
| - matched_args = [reversed(x) for x in [spec.args, spec.defaults or []]] |
156 |
| - spec_dict = dict(zip_longest(*matched_args, fillvalue=NO_DEFAULT)) |
157 |
| - # Pop context argument |
158 |
| - try: |
159 |
| - context_arg = arg_names.pop(0) |
160 |
| - except IndexError: |
| 143 | + # Rebuild signature with first arg dropped, or die usefully(ish trying |
| 144 | + sig = inspect.signature(func) |
| 145 | + params = list(sig.parameters.values()) |
| 146 | + # TODO: this ought to also check if an extant 1st param _was_ a Context |
| 147 | + # arg, and yell similarly if not. |
| 148 | + if not len(params): |
161 | 149 | # TODO: see TODO under __call__, this should be same type
|
162 | 150 | raise TypeError("Tasks must have an initial Context argument!")
|
163 |
| - del spec_dict[context_arg] |
164 |
| - return arg_names, spec_dict |
| 151 | + return sig.replace(parameters=params[1:]) |
165 | 152 |
|
166 | 153 | def fill_implicit_positionals(self, positional):
|
167 |
| - args, spec_dict = self.argspec(self.body) |
168 | 154 | # If positionals is None, everything lacking a default
|
169 | 155 | # value will be automatically considered positional.
|
170 | 156 | if positional is None:
|
171 |
| - positional = [] |
172 |
| - for name in args: # Go in defined order, not dict "order" |
173 |
| - default = spec_dict[name] |
174 |
| - if default is NO_DEFAULT: |
175 |
| - positional.append(name) |
| 157 | + positional = [ |
| 158 | + x.name |
| 159 | + for x in self.argspec(self.body).parameters.values() |
| 160 | + if x.default is inspect.Signature.empty |
| 161 | + ] |
176 | 162 | return positional
|
177 | 163 |
|
178 | 164 | def arg_opts(self, name, default, taken_names):
|
@@ -206,7 +192,7 @@ def arg_opts(self, name, default, taken_names):
|
206 | 192 | break
|
207 | 193 | opts["names"] = names
|
208 | 194 | # Handle default value & kind if possible
|
209 |
| - if default not in (None, NO_DEFAULT): |
| 195 | + if default not in (None, inspect.Signature.empty): |
210 | 196 | # TODO: allow setting 'kind' explicitly.
|
211 | 197 | # NOTE: skip setting 'kind' if optional is True + type(default) is
|
212 | 198 | # bool; that results in a nonsensical Argument which gives the
|
@@ -235,18 +221,17 @@ def get_arguments(self, ignore_unknown_help=None):
|
235 | 221 | Added the ``ignore_unknown_help`` kwarg.
|
236 | 222 | """
|
237 | 223 | # Core argspec
|
238 |
| - arg_names, spec_dict = self.argspec(self.body) |
239 |
| - # Obtain list of args + their default values (if any) in |
240 |
| - # declaration/definition order (i.e. based on getargspec()) |
241 |
| - tuples = [(x, spec_dict[x]) for x in arg_names] |
| 224 | + sig = self.argspec(self.body) |
242 | 225 | # Prime the list of all already-taken names (mostly for help in
|
243 | 226 | # choosing auto shortflags)
|
244 |
| - taken_names = {x[0] for x in tuples} |
| 227 | + taken_names = set(sig.parameters.keys()) |
245 | 228 | # Build arg list (arg_opts will take care of setting up shortnames,
|
246 | 229 | # etc)
|
247 | 230 | args = []
|
248 |
| - for name, default in tuples: |
249 |
| - new_arg = Argument(**self.arg_opts(name, default, taken_names)) |
| 231 | + for arg in sig.parameters.values(): |
| 232 | + new_arg = Argument( |
| 233 | + **self.arg_opts(arg.name, arg.default, taken_names) |
| 234 | + ) |
250 | 235 | args.append(new_arg)
|
251 | 236 | # Update taken_names list with new argument's full name list
|
252 | 237 | # (which may include new shortflags) so subsequent Argument
|
|
0 commit comments