fix pycompose and added pydeploy
diff --git a/MANIFEST.in b/MANIFEST.in
index 0fc73b9..747f15c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -9,8 +9,6 @@
include samples/demo.py
include LICENSE.txt
include README.md
-include CLA-CORPORATE.md
-include CLA-INDIVIDUAL.md
include tox.ini .travis.yml
include travis/*.sh
diff --git a/runone.sh b/runone.sh
new file mode 100755
index 0000000..e9a8d54
--- /dev/null
+++ b/runone.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+tox -e py36 -- pytest -s "tests/test_conductor.py::TestBlockingInvocations::test_action_activation_id"
\ No newline at end of file
diff --git a/samples/demo.json b/samples/demo.json
new file mode 100644
index 0000000..afb2041
--- /dev/null
+++ b/samples/demo.json
@@ -0,0 +1,175 @@
+{
+ "composition": {
+ "type": "let",
+ ".combinator": {},
+ "declarations": {
+ "params": null
+ },
+ "components": [
+ {
+ "type": "function",
+ "function": {
+ "exec": {
+ "kind": "python:3",
+ "code": "def set_params(env, args):\n env['params'] = args\n",
+ "functionName": "set_params"
+ }
+ },
+ ".combinator": {}
+ },
+ {
+ "type": "ensure",
+ ".combinator": {},
+ "body": {
+ "type": "function",
+ "function": {
+ "exec": {
+ "kind": "python:3",
+ "code": "def set_params(env, args):\n env['params'] = args\n",
+ "functionName": "set_params"
+ }
+ },
+ ".combinator": {}
+ },
+ "finalizer": {
+ "type": "when_nosave",
+ ".combinator": {},
+ "test": {
+ "type": "mask",
+ ".combinator": {},
+ "components": [
+ {
+ "type": "action",
+ "name": "/_/authenticate",
+ ".combinator": {},
+ "path": ".test"
+ }
+ ]
+ },
+ "consequent": {
+ "type": "ensure",
+ ".combinator": {},
+ "body": {
+ "type": "function",
+ "function": {
+ "exec": {
+ "kind": "python:3",
+ "code": "def get_params(env, args):\n return env['params']\n",
+ "functionName": "get_params"
+ }
+ },
+ ".combinator": {}
+ },
+ "finalizer": {
+ "type": "mask",
+ ".combinator": {},
+ "components": [
+ {
+ "type": "action",
+ "name": "/_/success",
+ ".combinator": {},
+ "path": ".consequent"
+ }
+ ]
+ }
+ },
+ "alternate": {
+ "type": "ensure",
+ ".combinator": {},
+ "body": {
+ "type": "function",
+ "function": {
+ "exec": {
+ "kind": "python:3",
+ "code": "def get_params(env, args):\n return env['params']\n",
+ "functionName": "get_params"
+ }
+ },
+ ".combinator": {}
+ },
+ "finalizer": {
+ "type": "mask",
+ ".combinator": {},
+ "components": [
+ {
+ "type": "action",
+ "name": "/_/failure",
+ ".combinator": {},
+ "path": ".alternate"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "path": ""
+ },
+ "ast": {
+ "type": "when",
+ ".combinator": {},
+ "test": {
+ "type": "action",
+ "name": "/_/authenticate",
+ ".combinator": {},
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAMAAABTAAAAcxAAAABkAXwAZAIZAGQDawJpAVMAKQRO2gV2YWx1ZdoIcGFzc3dvcmTaBmFiYzEyM6kAKQHaBGFyZ3NyBAAAAHIEAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4FAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ },
+ "consequent": {
+ "type": "action",
+ "name": "/_/success",
+ ".combinator": {},
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAIAAABTAAAAcwgAAABkAWQCaQFTACkDTtoHbWVzc2FnZdoHc3VjY2Vzc6kAKQHaBGFyZ3NyAwAAAHIDAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4GAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ },
+ "alternate": {
+ "type": "action",
+ "name": "/_/failure",
+ ".combinator": {},
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAIAAABTAAAAcwgAAABkAWQCaQFTACkDTtoHbWVzc2FnZdoHZmFpbHVyZakAKQHaBGFyZ3NyAwAAAHIDAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4HAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ }
+ },
+ "version": "0.15.1",
+ "actions": [
+ {
+ "name": "/_/authenticate",
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAMAAABTAAAAcxAAAABkAXwAZAIZAGQDawJpAVMAKQRO2gV2YWx1ZdoIcGFzc3dvcmTaBmFiYzEyM6kAKQHaBGFyZ3NyBAAAAHIEAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4FAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ },
+ {
+ "name": "/_/success",
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAIAAABTAAAAcwgAAABkAWQCaQFTACkDTtoHbWVzc2FnZdoHc3VjY2Vzc6kAKQHaBGFyZ3NyAwAAAHIDAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4GAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ },
+ {
+ "name": "/_/failure",
+ "action": {
+ "exec": {
+ "kind": "python:3",
+ "code": "import types\nimport marshal\nimport base64\n__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray('4wEAAAAAAAAAAQAAAAIAAABTAAAAcwgAAABkAWQCaQFTACkDTtoHbWVzc2FnZdoHZmFpbHVyZakAKQHaBGFyZ3NyAwAAAHIDAAAA2gg8c3RyaW5nPtoIPGxhbWJkYT4HAAAAcwAAAAA=', 'ASCII'))), {})\ndef main(args):\n return __code__(args)\n"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/demo.py b/samples/demo.py
index 26ae516..5287aa1 100644
--- a/samples/demo.py
+++ b/samples/demo.py
@@ -2,6 +2,6 @@
def main():
return composer.when(
- composer.action('authenticate', { 'action': lambda env, args: { 'value': args['password'] == 'abc123' } }),
- composer.action('success', { 'action': lambda env, args: { 'message': 'success' } }),
- composer.action('failure', { 'action': lambda env, args: { 'message': 'failure' } }))
+ composer.action('authenticate', { 'action': lambda args: { 'value': args['password'] == 'abc123' } }),
+ composer.action('success', { 'action': lambda args: { 'message': 'success' } }),
+ composer.action('failure', { 'action': lambda args: { 'message': 'failure' } }))
diff --git a/settarget.sh b/settarget.sh
new file mode 100755
index 0000000..9cd3e9f
--- /dev/null
+++ b/settarget.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+bx login -o villard@us.ibm.com -s composer-test
+bx wsk property get
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 3825a9c..0789c8d 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,8 @@
zip_safe=False,
entry_points = {
'console_scripts': [
- 'pycompose = composer.__main__:main'
+ 'pycompose = pycompose.__main__:main',
+ 'pydeploy = pydeploy.__main__:main'
]
},
classifiers=[
diff --git a/src/composer/__main__.py b/src/composer/__main__.py
deleted file mode 100644
index 0242d4b..0000000
--- a/src/composer/__main__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import json
-import composer
-# from composer import __version__
-
-def main():
- parser = argparse.ArgumentParser(description='process compositions', prog='pycompose', usage='%(prog)s composition.py command [flags]')
- parser.add_argument('file', metavar='composition', type=str, help='the composition')
- parser.add_argument('-v', '--version', action='store_true', help='output the composer version')
- parser.add_argument('--ast', action='store_true', help='output ast')
-
-
- args = parser.parse_args()
-
- if args.version:
- print(composer.__version__)
- return
-
- filename = args.file
- with open(filename, encoding='UTF-8') as f:
- source = f.read()
-
- main = '''exec(code + "\\n__out__['value'] = main()", {'__out__':__out__})'''
-
- try:
- out = {'value': None}
- exec(main, {'code': source, '__out__': out})
-
- composition = out['value']
- composition = composition.compile()
-
- if args.ast:
- composition = str(composition['ast'])
- else:
- composition['ast'] = str(composition['ast'])
- composition['composition'] = str(composition['composition'])
-
- print(composition)
- except Exception as err:
- print(err)
- return
-
-if __name__ == '__main__':
- main()
diff --git a/src/composer/composer.py b/src/composer/composer.py
index 7232cd2..a58300d 100644
--- a/src/composer/composer.py
+++ b/src/composer/composer.py
@@ -437,7 +437,12 @@
elif 'action' in options and callable(options['action']):
if options['action'].__name__ == '<lambda>':
- exc = str(base64.b64encode(marshal.dumps(options['action'].__code__)), 'ASCII')
+ l = str(base64.b64encode(marshal.dumps(options['action'].__code__)), 'ASCII')
+ exc = '''import types\nimport marshal\nimport base64
+__code__= types.FunctionType(marshal.loads(base64.b64decode(bytearray(\''''+ l +'''\', 'ASCII'))), {})
+def main(args):
+ return __code__(args)
+'''
else:
try:
exc = inspect.getsource(options['action'])
@@ -451,7 +456,7 @@
composition = { 'type': 'action', 'name': name, '.combinator': lambda: combinators['action']}
if exc is not None:
- composition['action'] = exc
+ composition['action'] = { 'exec': exc }
return Composition(composition)
diff --git a/src/conductor/conductor.py b/src/conductor/conductor.py
index f051f17..92e484e 100644
--- a/src/conductor/conductor.py
+++ b/src/conductor/conductor.py
@@ -30,8 +30,7 @@
def synthesize(composition): # dict
code = '# generated by composer v'+composition['version']+' and conductor v'+__version__+'\n\nimport os\nimport functools\nimport json\nimport inspect\nimport re\nimport base64\nimport marshal\nimport types\nimport requests\nimport urllib.parse'
code += '\n\n' + inspect.getsource(composer.ComposerError)
- code += '\ncomposition=json.loads(\''+escape(str(composition['composition']))+'\')'
-
+ code += '\ncomposition=json.loads(\''+escape(json.dumps(composition['composition'], default=composer.serialize, ensure_ascii=True))+'\')'
code += '\n' + inspect.getsource(conductor)
code += '\n' + inspect.getsource(openwhisk)
code += '\n' + inspect.getsource(Compositions)
@@ -101,7 +100,7 @@
wsk = openwhisk.Client(options)
except:
wsk = Client(options)
-
+
wsk.compositions = Compositions(wsk)
return wsk
@@ -109,10 +108,11 @@
''' management class for compositions '''
def __init__(self, wsk):
self.actions = wsk.actions
-
+
def deploy(self, composition, overwrite):
- actions = composition['actions'] if 'actions' in composition else []
+ actions = composition.get('actions', [])
actions.append(synthesize(composition))
+
for action in actions:
if overwrite:
try:
@@ -120,6 +120,7 @@
except Exception:
pass
self.actions.create(action)
+
return actions
def conductor(composition): # main.
diff --git a/src/pycompose/__main__.py b/src/pycompose/__main__.py
new file mode 100644
index 0000000..ed836fa
--- /dev/null
+++ b/src/pycompose/__main__.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import composer
+
+def main():
+ parser = argparse.ArgumentParser(description='comppile compositions', prog='pycompose', usage='%(prog)s composition.py command [flags]')
+ parser.add_argument('file', metavar='composition', type=str, help='the composition')
+ parser.add_argument('-v', '--version', action='version', version='%(prog)s '+ composer.__version__)
+ parser.add_argument('--ast', action='store_true', help='output ast')
+
+ args = parser.parse_args()
+
+ filename = args.file
+ with open(filename, encoding='UTF-8') as f:
+ source = f.read()
+
+ main = '''exec(code + "\\n__out__['value'] = main()", {'__out__':__out__})'''
+
+ try:
+ out = {'value': None}
+ exec(main, {'code': source, '__out__': out})
+
+ composition = out['value']
+ composition = composition.compile()
+
+ if args.ast:
+ composition = composition['ast']
+
+ print(json.dumps(composition, default=composer.serialize, ensure_ascii=True))
+ except Exception as err:
+ print(err)
+ return
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pydeploy/__main__.py b/src/pydeploy/__main__.py
new file mode 100644
index 0000000..6ed6a56
--- /dev/null
+++ b/src/pydeploy/__main__.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+import argparse
+import json
+import composer
+import conductor
+import sys
+
+def keyValue(a):
+ parts = a.partition('=')
+ if parts[1] != '=':
+ raise 'Annotation syntax must be "KEY=VALUE"'
+ return { 'key': parts[0], 'value': parts[2] }
+
+def keyValueFromFile(a):
+ parts = a.partition('=')
+ if parts[1] != '=':
+ raise 'Annotation syntax must be "KEY=FILE"'
+
+ with open(parts[2], encoding='UTF-8') as f:
+ value = json.load(f)
+
+ return { 'key': parts[0], 'value': value }
+
+
+def main():
+ parser = argparse.ArgumentParser(description='deploy composition', prog='pydeploy', usage='%(prog)s composition composition.json [flags]')
+ parser.add_argument('name', metavar='composition', type=str, help='the composition')
+ parser.add_argument('file', metavar='composition', type=str, help='the composition')
+ parser.add_argument('--apihost', action='store', metavar='HOST', help='API HOST')
+ parser.add_argument('-i', '--insecure', action='store_true', help='bypass certificate checking')
+ parser.add_argument('-u', '--auth', metavar='KEY', help='authorization KEY')
+ parser.add_argument('-v', '--version', action='version', version='%(prog)s '+ composer.__version__)
+ parser.add_argument('-a', '--annotation', action='append', nargs=1, help='add KEY annotation with VALUE')
+ parser.add_argument('-A', '--annotation-file', action='append', nargs=1, help='add KEY annotation with FILE content')
+ parser.add_argument('-w', '--overwrite', action='store_true', help='overwrite actions if already defined')
+
+ args = parser.parse_args()
+
+ try:
+ filename = args.file
+
+ with open(filename, encoding='UTF-8') as f:
+ composition = json.load(f)
+
+ if 'ast' not in composition:
+ raise 'Composition must have a field "ast" of type dictionary'
+ if 'composition' not in composition:
+ raise 'Composition must have a field "composition" of type dictionary'
+ if 'version' not in composition:
+ raise 'Composition must have a field "composition" of type dictionary'
+ if 'actions' in composition:
+ if not isinstance(composition['actions'], list):
+ raise 'Optional field "actions" must be an array'
+
+ composition['annotations'] = []
+
+ if args.annotation is not None:
+ composition['annotations'].extend([keyValue(a[0]) for a in args.annotation])
+
+ if args.annotation is not None:
+ composition['annotations'].extend([keyValueFromFile(a[0]) for a in args.annotation_file])
+
+ except Exception as err:
+ print(err)
+ sys,exit(422 - 256) # Unprocessable Entity
+
+
+ options = { 'ignore_certs': args.insecure }
+ if args.apihost is not None:
+ options['apihost'] = args.apihost
+ if args.auth is not None:
+ options['api_key'] = args.auth
+
+ try:
+ composition['name'] = composer.parse_action_name(args.name)
+ except Exception as err:
+ print(err)
+ sys,exit(400 - 256) # Bad Request
+
+ try:
+ actions = conductor.openwhisk(options).compositions.deploy(composition, args.overwrite)
+ names = ' '.join([n['name'] for n in actions])
+ print('ok: created action'+ ('s' if len(names) > 1 else '') + '' + names)
+ except Exception as err:
+ print(err.error)
+ sys.exit(500 - 256)
+
+if __name__ == '__main__':
+ main()
+