Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow annotations to be used for Python 3 Tasks
Browse files Browse the repository at this point in the history
Use inspect.signature instead of inspect.getargspect when
using Python 3 to allow function annotations to be used in tasks.

Also adds TaskContextError and fix missing context in Collection test.

Fixes #357
rsxm committed Aug 20, 2016
1 parent 322718d commit 189874d
Showing 4 changed files with 24 additions and 10 deletions.
2 changes: 1 addition & 1 deletion invoke/__init__.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
from .context import Context # noqa
from .exceptions import ( # noqa
AmbiguousEnvVar, ThreadException, ParseError, CollectionNotFound, # noqa
UnknownFileType, Exit, UncastableEnvVar, PlatformError, # noqa
UnknownFileType, Exit, UncastableEnvVar, PlatformError, TaskContextError # noqa
) # noqa
from .executor import Executor # noqa
from .loader import FilesystemLoader # noqa
7 changes: 7 additions & 0 deletions invoke/exceptions.py
Original file line number Diff line number Diff line change
@@ -106,6 +106,13 @@ class UnknownFileType(Exception):
pass


class TaskContextError(TypeError):
"""
Raised when the first positional argument of a task is missing or not an instance
of Context
"""
pass

#: A namedtuple wrapping a thread-borne exception & that thread's arguments.
ExceptionWrapper = namedtuple(
'ExceptionWrapper',
23 changes: 15 additions & 8 deletions invoke/tasks.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
from .vendor import six

from .context import Context
from .exceptions import TaskContextError
from .parser import Argument, translate_underscores

if six.PY3:
@@ -106,8 +107,7 @@ def __call__(self, *args, **kwargs):
# Guard against calling tasks with no context.
if not isinstance(args[0], Context):
err = "Task expected a Context, got {0} instead!"
# TODO: raise a custom subclass _of_ TypeError instead
raise TypeError(err.format(type(args[0])))
raise TaskContextError(err.format(type(args[0])))
result = self.body(*args, **kwargs)
self.times_called += 1
return result
@@ -132,16 +132,23 @@ def argspec(self, body):
# TODO: __call__ exhibits the 'self' arg; do we manually nix 1st result
# in argspec, or is there a way to get the "really callable" spec?
func = body if isinstance(body, types.FunctionType) else body.__call__
spec = inspect.getargspec(func)
arg_names = spec.args[:]
matched_args = [reversed(x) for x in [spec.args, spec.defaults or []]]
spec_dict = dict(zip_longest(*matched_args, fillvalue=NO_DEFAULT))
if six.PY3:
sig = inspect.signature(func)
arg_names = [k for k, v in sig.parameters.items()]
spec_dict = {}
for k, v in sig.parameters.items():
spec_dict.update(
{k: v.default if not v.default == sig.empty else NO_DEFAULT})
else:
spec = inspect.getargspec(func)
arg_names = spec.args[:]
matched_args = [reversed(x) for x in [spec.args, spec.defaults or []]]
spec_dict = dict(zip_longest(*matched_args, fillvalue=NO_DEFAULT))
# Pop context argument
try:
context_arg = arg_names.pop(0)
except IndexError:
# TODO: see TODO under __call__, this should be same type
raise TypeError("Tasks must have an initial Context argument!")
raise TaskContextError("Tasks must have an initial Context argument!")
del spec_dict[context_arg]
return arg_names, spec_dict

2 changes: 1 addition & 1 deletion tests/collection.py
Original file line number Diff line number Diff line change
@@ -229,7 +229,7 @@ def prefers_task_name_attr_over_function_name(self):
def raises_ValueError_if_no_name_found(self):
# Can't use a lambda here as they are technically real functions.
class Callable(object):
def __call__(self):
def __call__(self, ctx):
pass
self.c.add_task(Task(Callable()))

0 comments on commit 189874d

Please sign in to comment.