Add composer.task
diff --git a/src/composer/__init__.py b/src/composer/__init__.py
index bcab68e..93d7c10 100644
--- a/src/composer/__init__.py
+++ b/src/composer/__init__.py
@@ -14,6 +14,9 @@
 def literal(value):
     return _composer.literal(value)
 
+def task(value):
+    return _composer.task(value)
+
 def function(value):
     return _composer.function(value)
 
diff --git a/src/composer/composer.py b/src/composer/composer.py
index abc18af..5cfc94c 100644
--- a/src/composer/composer.py
+++ b/src/composer/composer.py
@@ -77,12 +77,12 @@
                     setattr(self, arg['_'], f(getattr(self, arg['_']), arg['_']))
 
 class Compiler:
-    def literal(self, value):
-        return self._compose('literal', (value,))
-
     def empty(self):
         return self._compose('empty', ())
 
+    def literal(self, value):
+        return self._compose('literal', (value,))
+
     def seq(self, *arguments):
         return self._compose('seq', arguments)
 
@@ -100,7 +100,8 @@
         if isinstance(task, Composition):
             return task
 
-        # if (typeof task === 'function') return this.function(task)
+        if callable(task):
+            return self.function(task)
 
         if isinstance(task, str): # python3 only
             return self.action(task)
@@ -214,15 +215,39 @@
 
         if 'actions' in obj:
             for action in obj['actions']:
+                print('ACTION:', action)
                 action['serializer'] = serialize
                 self.actions.delete(action)
                 self.actions.update(action)
 
 class Composer(Compiler):
+    def action(self, name, options = {}):
+        ''' enhanced action combinator: mangle name, capture code '''
+        name = parse_action_name(name) # throws ComposerError if name is not valid
+        exec = None
+        if hasattr(options, 'sequence'): # native sequence
+            exec = { 'kind': 'sequence', 'components': tuple(map(parse_action_name, options['sequence'])) }
 
-    # return enhanced openwhisk client capable of deploying compositions
+        if hasattr(options, 'filename') and isinstance(options['filename'], str): # read action code from file
+            raise ComposerError('read from file not implemented')
+            # exec = fs.readFileSync(options.filename, { encoding: 'utf8' })
+
+        # if (typeof options.action === 'function') { // capture function
+        #     exec = `const main = ${options.action}`
+        #     if (exec.indexOf('[native code]') !== -1) throw new ComposerError('Cannot capture native function', options.action)
+        # }
+
+        if hasattr(options, 'action') and (isinstance(options['action'], str) or isinstance(options['action'],  dict)):
+            exec = options['action']
+
+        if isinstance(exec, str):
+            exec = { 'kind': 'nodejs:default', 'code': exec }
+
+        return Composition(type='action', exec=exec, name=name)
+
     def openwhisk(self, options):
-        ''' try to extract apihost and key first from whisk property file file and then from os.environ '''
+        ''' return enhanced openwhisk client capable of deploying compositions '''
+        # try to extract apihost and key first from whisk property file file and then from os.environ
 
         wskpropsPath = os.environ['WSK_CONFIG_FILE'] if 'WSK_CONFIG_FILE' in os.environ else os.path.expanduser('~/.wskprops')
         with open(wskpropsPath) as f:
diff --git a/src/openwhisk/openwhisk.py b/src/openwhisk/openwhisk.py
index 4c974bd..5c57afd 100644
--- a/src/openwhisk/openwhisk.py
+++ b/src/openwhisk/openwhisk.py
@@ -56,7 +56,7 @@
 
         serializer = options['serializer'] if 'serializer' in options else None
         payload = json.dumps(body, default=serializer)
-
+        print('\n\nPAYLOAD\n\n:', payload)
         headers = { 'Authorization': self.auth_header(), 'Content-Type': 'application/json' }
         verify = not self.options['ignore_certs']
 
@@ -66,7 +66,8 @@
             # we turn >=400 statusCode responses into exceptions
             error = Exception()
             error.status_code = resp.status_code
-            error.error = resp.text
+            error.error = resp.reason
+            print('ERROR', resp.reason)
             raise error
         else:
             # otherwise, the response body is the expected return value
@@ -230,10 +231,10 @@
         if 'action' not in options:
             raise Exception(missing_action_body_error)
 
-        body = { 'exec': { 'kind': options['kind'] if 'kind' in options else 'nodejs:default', 'code': options['action'] } }
+        body = { 'exec': { 'kind': options['kind'] if 'kind' in options else 'python:default', 'code': options['action'] } }
 
         if isinstance(options['action'], bytes):
-            body['exec']['code'] =base64.encodebytes(options['action'])
+            body['exec']['code'] = base64.encodebytes(options['action'])
         elif isinstance(options['action'], dict):
             return options['action']
 
diff --git a/tests/test_composer.py b/tests/test_composer.py
index bb67322..8d1cb34 100644
--- a/tests/test_composer.py
+++ b/tests/test_composer.py
@@ -9,7 +9,10 @@
 def define(action):
     ''' deploy action '''
 
-    wsk.actions.delete(action['name'])
+    try:
+        wsk.actions.delete(action['name'])
+    except:
+        pass
     wsk.actions.create(action)
 
 def invoke(task, params = {}, blocking = True):
@@ -17,35 +20,43 @@
    wsk.compositions.deploy(composer.composition(name, task))
    return wsk.actions.invoke({ 'name': name, 'params': params, 'blocking': blocking })
 
+@pytest.fixture(scope="session", autouse=True)
+def deploy_actions():
+    define({ 'name': 'echo', 'action': 'const main = x=>x', 'kind': 'nodejs:default' })
+    define({ 'name': 'DivideByTwo', 'action': 'function main({n}) { return { n: n / 2 } }', 'kind': 'nodejs:default'})
+    define({ 'name': 'TripleAndIncrement', 'action': 'function main({n}) { return { n: n * 3 + 1 } }', 'kind': 'nodejs:default' })
+    define({ 'name': 'isNotOne', 'action': 'function main({n}) { return { value: n != 1 } }', 'kind': 'nodejs:default' })
+    define({ 'name': 'isEven', 'action': 'function main({n}) { return { value: n % 2 == 0 } }', 'kind': 'nodejs:default'})
 
-def test_parse_action_name():
-    combos = [
-        { "n": "", "s": False, "e": "Name is not specified" },
-        { "n": " ", "s": False, "e": "Name is not specified" },
-        { "n": "/", "s": False, "e": "Name is not valid" },
-        { "n": "//", "s": False, "e": "Name is not valid" },
-        { "n": "/a", "s": False, "e": "Name is not valid" },
-        { "n": "/a/b/c/d", "s": False, "e": "Name is not valid" },
-        { "n": "/a/b/c/d/", "s": False, "e": "Name is not valid" },
-        { "n": "a/b/c/d", "s": False, "e": "Name is not valid" },
-        { "n": "/a/ /b", "s": False, "e": "Name is not valid" },
-        { "n": "a", "e": False, "s": "/_/a" },
-        { "n": "a/b", "e": False, "s": "/_/a/b" },
-        { "n": "a/b/c", "e": False, "s": "/a/b/c" },
-        { "n": "/a/b", "e": False, "s": "/a/b" },
-        { "n": "/a/b/c", "e": False, "s": "/a/b/c" }
-    ]
-    for combo in combos:
-        if combo["s"] is not False:
-            # good cases
-            assert composer.parse_action_name(combo["n"]) == combo["s"]
-        else:
-            # error cases
-            try:
-                composer.parse_action_name(combo["n"])
-                assert False
-            except composer.ComposerError as error:
-                assert error.message == combo["e"]
+class TestAction:
+    def test_parse_action_name(self):
+        combos = [
+            { "n": "", "s": False, "e": "Name is not specified" },
+            { "n": " ", "s": False, "e": "Name is not specified" },
+            { "n": "/", "s": False, "e": "Name is not valid" },
+            { "n": "//", "s": False, "e": "Name is not valid" },
+            { "n": "/a", "s": False, "e": "Name is not valid" },
+            { "n": "/a/b/c/d", "s": False, "e": "Name is not valid" },
+            { "n": "/a/b/c/d/", "s": False, "e": "Name is not valid" },
+            { "n": "a/b/c/d", "s": False, "e": "Name is not valid" },
+            { "n": "/a/ /b", "s": False, "e": "Name is not valid" },
+            { "n": "a", "e": False, "s": "/_/a" },
+            { "n": "a/b", "e": False, "s": "/_/a/b" },
+            { "n": "a/b/c", "e": False, "s": "/a/b/c" },
+            { "n": "/a/b", "e": False, "s": "/a/b" },
+            { "n": "/a/b/c", "e": False, "s": "/a/b/c" }
+        ]
+        for combo in combos:
+            if combo["s"] is not False:
+                # good cases
+                assert composer.parse_action_name(combo["n"]) == combo["s"]
+            else:
+                # error cases
+                try:
+                    composer.parse_action_name(combo["n"])
+                    assert False
+                except composer.ComposerError as error:
+                    assert error.message == combo["e"]
 
 @pytest.mark.literal
 class TestLiteral:
@@ -73,3 +84,26 @@
          activation = invoke(composer.function(lambda args: args['n'] % 2 == 0), { 'n': 4 })
          assert activation['response']['result'] == { 'value': True }
 
+class TestTasks:
+
+    def test_task_action(self):
+        activation = invoke(composer.task('isNotOne'), { 'n': 0 })
+        assert activation['response']['result'] == { 'value': True }
+
+    @pytest.mark.skip(reason='need python conductor')
+    def test_task_function(self):
+        activation = invoke(composer.task(lambda args: args['n'] % 2 == 0), { 'n': 4 })
+        assert activation['response']['result'] == { 'value': True }
+
+    def test_task_none(self):
+        activation = invoke(composer.task(None), { 'foo': 'foo' })
+        assert activation['response']['result'] == { 'foo': 'foo' }
+
+    def test_task_fail(self):
+        '''none task must fail on error input'''
+        try:
+            invoke(composer.task(None), { 'error': 'foo' })
+            assert False
+        except Exception as error:
+            print(error)
+