Skip to content

Commit 87f9504

Browse files
committed
Replace inspect.getargspec with inspect.signature
FINALLY
1 parent b2fcb98 commit 87f9504

File tree

3 files changed

+37
-39
lines changed

3 files changed

+37
-39
lines changed

invoke/tasks.py

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@
1717
from itertools import izip_longest as zip_longest
1818

1919

20-
#: Sentinel object representing a truly blank value (vs ``None``).
21-
NO_DEFAULT = object()
22-
23-
2420
class Task(object):
2521
"""
2622
Core object representing an executable task & its argument specification.
@@ -134,45 +130,40 @@ def called(self):
134130

135131
def argspec(self, body):
136132
"""
137-
Returns two-tuple:
138-
139-
* First item is list of arg names, in order defined.
133+
Returns a modified `inspect.Signature` based on that of ``body``.
140134
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).
135+
:returns:
136+
an `inspect.Signature` matching that of ``body``, but with the
137+
initial context argument removed.
138+
:raises TypeError:
139+
if the task lacks an initial positional `.Context` argument.
146140
147141
.. versionadded:: 1.0
142+
.. versionchanged:: 2.0
143+
Changed from returning a two-tuple of ``(arg_names, spec_dict)`` to
144+
returning an `inspect.Signature`.
148145
"""
149146
# 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?
152147
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:
148+
# Rebuild signature with first arg dropped, or die usefully(ish trying
149+
sig = inspect.signature(func)
150+
params = list(sig.parameters.values())
151+
# TODO: this ought to also check if an extant 1st param _was_ a Context
152+
# arg, and yell similarly if not.
153+
if not len(params):
161154
# TODO: see TODO under __call__, this should be same type
162155
raise TypeError("Tasks must have an initial Context argument!")
163-
del spec_dict[context_arg]
164-
return arg_names, spec_dict
156+
return sig.replace(parameters=params[1:])
165157

166158
def fill_implicit_positionals(self, positional):
167-
args, spec_dict = self.argspec(self.body)
168159
# If positionals is None, everything lacking a default
169160
# value will be automatically considered positional.
170161
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)
162+
positional = [
163+
x.name
164+
for x in self.argspec(self.body).parameters.values()
165+
if x.default is inspect.Signature.empty
166+
]
176167
return positional
177168

178169
def arg_opts(self, name, default, taken_names):
@@ -206,7 +197,7 @@ def arg_opts(self, name, default, taken_names):
206197
break
207198
opts["names"] = names
208199
# Handle default value & kind if possible
209-
if default not in (None, NO_DEFAULT):
200+
if default not in (None, inspect.Signature.empty):
210201
# TODO: allow setting 'kind' explicitly.
211202
# NOTE: skip setting 'kind' if optional is True + type(default) is
212203
# bool; that results in a nonsensical Argument which gives the
@@ -235,18 +226,17 @@ def get_arguments(self, ignore_unknown_help=None):
235226
Added the ``ignore_unknown_help`` kwarg.
236227
"""
237228
# 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]
229+
sig = self.argspec(self.body)
242230
# Prime the list of all already-taken names (mostly for help in
243231
# choosing auto shortflags)
244-
taken_names = {x[0] for x in tuples}
232+
taken_names = set(sig.parameters.keys())
245233
# Build arg list (arg_opts will take care of setting up shortnames,
246234
# etc)
247235
args = []
248-
for name, default in tuples:
249-
new_arg = Argument(**self.arg_opts(name, default, taken_names))
236+
for arg in sig.parameters.values():
237+
new_arg = Argument(
238+
**self.arg_opts(arg.name, arg.default, taken_names)
239+
)
250240
args.append(new_arg)
251241
# Update taken_names list with new argument's full name list
252242
# (which may include new shortflags) so subsequent Argument

sites/www/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Changelog
33
=========
44

5+
- :suppport:`-` `Task.argspec <invoke.tasks.Task.argspec>` has changed its
6+
return value; it now returns an `inspect.Signature` derived from that of the
7+
task's body callable.
8+
9+
.. warning::
10+
This change is backwards incompatible if you were using this method
11+
directly.
12+
513
- :release:`1.7.3 <2022-09-30>`
614
- :support:`- backported` Fix a non-fatal bug in our setup.py
715
``long_description`` generation causing 1.7.0-1.7.2 to have malformed

tests/collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def prefers_task_name_attr_over_function_name(self):
305305
def raises_ValueError_if_no_name_found(self):
306306
# Can't use a lambda here as they are technically real functions.
307307
class Callable(object):
308-
def __call__(self):
308+
def __call__(self, ctx):
309309
pass
310310

311311
with raises(ValueError):

0 commit comments

Comments
 (0)