This is an automated email from the ASF dual-hosted git repository. jbonofre pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-liminal.git
commit 14716fcab2ad7f1140d14333e6435bc158e779e8 Author: aviemzur <[email protected]> AuthorDate: Mon Mar 16 10:51:56 2020 +0200 Add python_server service --- rainbow/build/build_rainbows.py | 51 ++++++--- .../hello_world => rainbow/build/http}/__init__.py | 0 rainbow/build/http/python/Dockerfile | 24 +++++ rainbow/{http => build/http/python}/__init__.py | 0 .../build/http/python/python_server_image.py | 29 +++-- .../http/python/python_server_requirements.txt | 2 + rainbow/build/http/python/rainbow_python_server.py | 62 +++++++++++ rainbow/build/image_builder.py | 118 +++++++++++++++++++++ rainbow/build/python/python_image.py | 62 ++--------- requirements.txt | 5 +- .../hello_world => build/http}/__init__.py | 0 .../hello_world => build/http/python}/__init__.py | 0 .../build/http/python/test_python_server_image.py | 105 ++++++++++++++++++ .../airflow/build/python/test_python_image.py | 11 +- tests/runners/airflow/build/test_build_rainbow.py | 27 ----- tests/runners/airflow/build/test_build_rainbows.py | 56 ++++++++++ .../{hello_world => helloworld}/__init__.py | 0 .../{hello_world => helloworld}/hello_world.py | 0 .../rainbow/{hello_world => myserver}/__init__.py | 0 .../__init__.py => myserver/my_server.py} | 4 + tests/runners/airflow/rainbow/rainbow.yml | 29 +++-- tests/runners/airflow/tasks/test_python.py | 2 +- 22 files changed, 465 insertions(+), 122 deletions(-) diff --git a/rainbow/build/build_rainbows.py b/rainbow/build/build_rainbows.py index 2a9e6a3..c4a14c7 100644 --- a/rainbow/build/build_rainbows.py +++ b/rainbow/build/build_rainbows.py @@ -20,36 +20,59 @@ import os import yaml +from rainbow.build.http.python.python_server_image import PythonServerImageBuilder +from rainbow.build.python.python_image import PythonImageBuilder from rainbow.core.util import files_util -from rainbow.build.python.python_image import PythonImage def build_rainbows(path): """ - TODO: doc for build_rainbow + TODO: doc for build_rainbows """ - config_files = files_util.find_config_files(path) for config_file in config_files: print(f'Building artifacts for file: {config_file}') + base_path = os.path.dirname(config_file) + with open(config_file) as stream: - config = yaml.safe_load(stream) + rainbow_config = yaml.safe_load(stream) - for pipeline in config['pipelines']: + for pipeline in rainbow_config['pipelines']: for task in pipeline['tasks']: - task_type = task['type'] - task_instance = get_build_class(task_type)() - task_instance.build(base_path=os.path.dirname(config_file), - relative_source_path=task['source'], - tag=task['image']) + builder_class = __get_task_build_class(task['type']) + __build_image(base_path, task, builder_class) + + for service in rainbow_config['services']: + builder_class = __get_service_build_class(service['type']) + __build_image(base_path, service, builder_class) + + +def __build_image(base_path, builder_config, builder): + if 'source' in builder_config: + server_builder_instance = builder( + config=builder_config, + base_path=base_path, + relative_source_path=builder_config['source'], + tag=builder_config['image']) + server_builder_instance.build() + else: + print(f"No source provided for {builder_config['name']}, skipping.") -build_classes = { - 'python': PythonImage +__task_build_classes = { + 'python': PythonImageBuilder, } +__service_build_classes = { + 'python_server': PythonServerImageBuilder +} + + +def __get_task_build_class(task_type): + return __task_build_classes[task_type] + -def get_build_class(task_type): - return build_classes[task_type] +def __get_service_build_class(task_type): + return __service_build_classes[task_type] diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/rainbow/build/http/__init__.py similarity index 100% copy from tests/runners/airflow/rainbow/hello_world/__init__.py copy to rainbow/build/http/__init__.py diff --git a/rainbow/build/http/python/Dockerfile b/rainbow/build/http/python/Dockerfile new file mode 100644 index 0000000..6119437 --- /dev/null +++ b/rainbow/build/http/python/Dockerfile @@ -0,0 +1,24 @@ +# Use an official Python runtime as a parent image +FROM python:3.7-slim + +# Install aptitude build-essential +#RUN apt-get install -y --reinstall build-essential + +# Set the working directory to /app +WORKDIR /app + +# Order of operations is important here for docker's caching & incremental build performance. ! +# Be careful when changing this code. ! + +# Install any needed packages specified in python_server_requirements.txt and requirements.txt +COPY ./python_server_requirements.txt /app +RUN pip install -r python_server_requirements.txt + +COPY ./requirements.txt /app +RUN pip install -r requirements.txt + +# Copy the current directory contents into the container at /app +RUN echo "Copying source code.." +COPY . /app + +CMD python -u rainbow_python_server.py diff --git a/rainbow/http/__init__.py b/rainbow/build/http/python/__init__.py similarity index 100% rename from rainbow/http/__init__.py rename to rainbow/build/http/python/__init__.py diff --git a/tests/runners/airflow/rainbow/hello_world/hello_world.py b/rainbow/build/http/python/python_server_image.py similarity index 51% copy from tests/runners/airflow/rainbow/hello_world/hello_world.py copy to rainbow/build/http/python/python_server_image.py index 3eae465..9a65477 100644 --- a/tests/runners/airflow/rainbow/hello_world/hello_world.py +++ b/rainbow/build/http/python/python_server_image.py @@ -15,13 +15,28 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import json -print('Hello world!') -print() +import os -with open('/rainbow_input.json') as file: - print(json.loads(file.readline())) +from rainbow.build.image_builder import ImageBuilder +import yaml -with open('/output.json', 'w') as file: - file.write(json.dumps({'a': 1, 'b': 2})) + +class PythonServerImageBuilder(ImageBuilder): + + def __init__(self, config, base_path, relative_source_path, tag): + super().__init__(config, base_path, relative_source_path, tag) + + @staticmethod + def _dockerfile_path(): + return os.path.join(os.path.dirname(__file__), 'Dockerfile') + + @staticmethod + def _additional_files_from_paths(): + return [ + os.path.join(os.path.dirname(__file__), 'rainbow_python_server.py'), + os.path.join(os.path.dirname(__file__), 'python_server_requirements.txt') + ] + + def _additional_files_from_filename_content_pairs(self): + return [('service.yml', yaml.safe_dump(self.config))] diff --git a/rainbow/build/http/python/python_server_requirements.txt b/rainbow/build/http/python/python_server_requirements.txt new file mode 100644 index 0000000..c395de6 --- /dev/null +++ b/rainbow/build/http/python/python_server_requirements.txt @@ -0,0 +1,2 @@ +Flask==1.1.1 +pyyaml diff --git a/rainbow/build/http/python/rainbow_python_server.py b/rainbow/build/http/python/rainbow_python_server.py new file mode 100644 index 0000000..66aab27 --- /dev/null +++ b/rainbow/build/http/python/rainbow_python_server.py @@ -0,0 +1,62 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import yaml +from flask import Flask + +app = Flask(__name__) + + +def start_server(yml_path): + with open(yml_path) as stream: + __start_server(yaml.safe_load(stream)) + + +def __start_server(config): + endpoints = config['endpoints'] + + for endpoint_config in endpoints: + print(f'Registering endpoint: {endpoint_config}') + endpoint = endpoint_config['endpoint'] + + print(endpoint_config['module']) + + module = __get_module(endpoint_config['module']) + function = module.__getattribute__(endpoint_config['function']) + + app.add_url_rule(rule=endpoint, + endpoint=endpoint, + view_func=function, + methods=['GET', 'POST']) + + print('Starting python server') + + app.run(host='0.0.0.0', threaded=False, port=80) + + +def __get_module(kls): + parts = kls.split('.') + module = ".".join(parts) + m = __import__(module) + for comp in parts[1:]: + m = getattr(m, comp) + return m + + +if __name__ == "__main__": + start_server('service.yml') diff --git a/rainbow/build/image_builder.py b/rainbow/build/image_builder.py new file mode 100644 index 0000000..b54dc00 --- /dev/null +++ b/rainbow/build/image_builder.py @@ -0,0 +1,118 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import shutil +import tempfile + +import docker + + +class ImageBuilder: + """ + Builds an image from source code + """ + + def __init__(self, config, base_path, relative_source_path, tag): + """ + TODO: pydoc + + :param config: + :param base_path: + :param relative_source_path: + :param tag: + """ + self.base_path = base_path + self.relative_source_path = relative_source_path + self.tag = tag + self.config = config + + def build(self): + """ + Builds source code into an image. + """ + print(f'[ ] Building image: {self.tag}') + + temp_dir = self.__temp_dir() + + self.__copy_source_code(temp_dir) + self.__write_additional_files(temp_dir) + + # TODO: log docker output + docker_client = docker.from_env() + docker_client.images.build(path=temp_dir, tag=self.tag) + docker_client.close() + + self.__remove_dir(temp_dir) + + print(f'[X] Building image: {self.tag} (Success).') + + def __copy_source_code(self, temp_dir): + self.__copy_dir(os.path.join(self.base_path, self.relative_source_path), temp_dir) + + def __write_additional_files(self, temp_dir): + # TODO: move requirements.txt related code to a parent class for python image builders. + requirements_file_path = os.path.join(temp_dir, 'requirements.txt') + if not os.path.exists(requirements_file_path): + with open(requirements_file_path, 'w'): + pass + + for file in [self._dockerfile_path()] + self._additional_files_from_paths(): + self.__copy_file(file, temp_dir) + + for filename, content in self._additional_files_from_filename_content_pairs(): + with open(os.path.join(temp_dir, filename), 'w') as file: + file.write(content) + + def __temp_dir(self): + temp_dir = tempfile.mkdtemp() + # Delete dir for shutil.copytree to work + self.__remove_dir(temp_dir) + return temp_dir + + @staticmethod + def __remove_dir(temp_dir): + shutil.rmtree(temp_dir) + + @staticmethod + def __copy_dir(source_path, destination_path): + shutil.copytree(source_path, destination_path) + + @staticmethod + def __copy_file(source_file_path, destination_file_path): + shutil.copy2(source_file_path, destination_file_path) + + @staticmethod + def _dockerfile_path(): + """ + Path to Dockerfile + """ + raise NotImplementedError() + + @staticmethod + def _additional_files_from_paths(): + """ + List of paths to additional files + """ + return [] + + def _additional_files_from_filename_content_pairs(self): + """ + File name and content pairs to create files from + """ + return [] diff --git a/rainbow/build/python/python_image.py b/rainbow/build/python/python_image.py index f0fb3a0..d856b8c 100644 --- a/rainbow/build/python/python_image.py +++ b/rainbow/build/python/python_image.py @@ -17,62 +17,22 @@ # under the License. import os -import shutil -import tempfile -import docker +from rainbow.build.image_builder import ImageBuilder -class PythonImage: +class PythonImageBuilder(ImageBuilder): - def build(self, base_path, relative_source_path, tag): - """ - TODO: pydoc - - :param base_path: - :param relative_source_path: - :param tag: - :param extra_files: - :return: - """ - - print(f'Building image {tag}') - - temp_dir = tempfile.mkdtemp() - # Delete dir for shutil.copytree to work - os.rmdir(temp_dir) - - self.__copy_source(os.path.join(base_path, relative_source_path), temp_dir) - - requirements_file_path = os.path.join(temp_dir, 'requirements.txt') - if not os.path.exists(requirements_file_path): - with open(requirements_file_path, 'w'): - pass - - docker_files = [ - os.path.join(os.path.dirname(__file__), 'Dockerfile'), - os.path.join(os.path.dirname(__file__), 'container-setup.sh'), - os.path.join(os.path.dirname(__file__), 'container-teardown.sh') - ] - - for file in docker_files: - self.__copy_file(file, temp_dir) - - docker_client = docker.from_env() - - # TODO: log docker output - docker_client.images.build(path=temp_dir, tag=tag) - - docker_client.close() - - print(temp_dir, os.listdir(temp_dir)) - - shutil.rmtree(temp_dir) + def __init__(self, config, base_path, relative_source_path, tag): + super().__init__(config, base_path, relative_source_path, tag) @staticmethod - def __copy_source(source_path, destination_path): - shutil.copytree(source_path, destination_path) + def _dockerfile_path(): + return os.path.join(os.path.dirname(__file__), 'Dockerfile') @staticmethod - def __copy_file(source_file_path, destination_file_path): - shutil.copy2(source_file_path, destination_file_path) + def _additional_files_from_paths(): + return [ + os.path.join(os.path.dirname(__file__), 'container-setup.sh'), + os.path.join(os.path.dirname(__file__), 'container-teardown.sh'), + ] diff --git a/requirements.txt b/requirements.txt index 599ab8b..dd1e232 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ -botocore -PyYAML +botocore==1.15.21 docker==4.2.0 apache-airflow==1.10.9 docker-pycreds==0.4.0 click==7.1.1 +Flask=1.1.1 +pyyaml \ No newline at end of file diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/tests/runners/airflow/build/http/__init__.py similarity index 100% copy from tests/runners/airflow/rainbow/hello_world/__init__.py copy to tests/runners/airflow/build/http/__init__.py diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/tests/runners/airflow/build/http/python/__init__.py similarity index 100% copy from tests/runners/airflow/rainbow/hello_world/__init__.py copy to tests/runners/airflow/build/http/python/__init__.py diff --git a/tests/runners/airflow/build/http/python/test_python_server_image.py b/tests/runners/airflow/build/http/python/test_python_server_image.py new file mode 100644 index 0000000..fd38c80 --- /dev/null +++ b/tests/runners/airflow/build/http/python/test_python_server_image.py @@ -0,0 +1,105 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import threading +import time +import unittest +import urllib.request +from unittest import TestCase + +import docker + +from rainbow.build.http.python.python_server_image import PythonServerImageBuilder + + +class TestPythonServer(TestCase): + + def setUp(self) -> None: + super().setUp() + self.docker_client = docker.from_env() + self.config = self.__create_conf('my_task') + self.image_name = self.config['image'] + self.__remove_containers() + + def tearDown(self) -> None: + self.__remove_containers() + self.docker_client.close() + + def test_build_python_server(self): + builder = PythonServerImageBuilder(config=self.config, + base_path='tests/runners/airflow/rainbow', + relative_source_path='myserver', + tag=self.image_name) + + builder.build() + + thread = threading.Thread(target=self.__run_container, args=[self.image_name]) + thread.daemon = True + thread.start() + + time.sleep(2) + + server_response = urllib.request.urlopen("http://localhost:9294/myendpoint1").read() + + self.assertEqual("b'1'", str(server_response)) + + def __remove_containers(self): + print(f'Stopping containers with image: {self.image_name}') + + all_containers = self.docker_client.containers + matching_containers = all_containers.list(filters={'ancestor': self.image_name}) + + for container in matching_containers: + container_id = container.id + print(f'Stopping container {container_id}') + self.docker_client.api.stop(container_id) + print(f'Removing container {container_id}') + self.docker_client.api.remove_container(container_id) + + self.docker_client.containers.prune() + + def __run_container(self, image_name): + try: + print(f'Running container for image: {image_name}') + self.docker_client.containers.run(image_name, ports={'80/tcp': 9294}) + except Exception as err: + print(err) + pass + + @staticmethod + def __create_conf(task_id): + return { + 'task': task_id, + 'cmd': 'foo bar', + 'image': 'rainbow_server_image', + 'source': 'tests/runners/airflow/rainbow/myserver', + 'input_type': 'my_input_type', + 'input_path': 'my_input', + 'output_path': '/my_output.json', + 'endpoints': [ + { + 'endpoint': '/myendpoint1', + 'module': 'my_server', + 'function': 'myendpoint1func' + } + ] + } + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/runners/airflow/build/python/test_python_image.py b/tests/runners/airflow/build/python/test_python_image.py index d190fba..ff4555d 100644 --- a/tests/runners/airflow/build/python/test_python_image.py +++ b/tests/runners/airflow/build/python/test_python_image.py @@ -19,7 +19,7 @@ from unittest import TestCase import docker -from rainbow.build.python.python_image import PythonImage +from rainbow.build.python.python_image import PythonImageBuilder class TestPythonImage(TestCase): @@ -29,7 +29,12 @@ class TestPythonImage(TestCase): image_name = config['image'] - PythonImage().build('tests/runners/airflow/rainbow', 'hello_world', image_name) + builder = PythonImageBuilder(config=config, + base_path='tests/runners/airflow/rainbow', + relative_source_path='helloworld', + tag=image_name) + + builder.build() # TODO: elaborate test of image, validate input/output @@ -54,7 +59,7 @@ class TestPythonImage(TestCase): 'task': task_id, 'cmd': 'foo bar', 'image': 'rainbow_image', - 'source': 'tests/runners/airflow/rainbow/hello_world', + 'source': 'tests/runners/airflow/rainbow/helloworld', 'input_type': 'my_input_type', 'input_path': 'my_input', 'output_path': '/my_output.json' diff --git a/tests/runners/airflow/build/test_build_rainbow.py b/tests/runners/airflow/build/test_build_rainbow.py deleted file mode 100644 index 0817d6c..0000000 --- a/tests/runners/airflow/build/test_build_rainbow.py +++ /dev/null @@ -1,27 +0,0 @@ -import unittest -from unittest import TestCase - -import docker -from rainbow.build import build_rainbows - - -class TestBuildRainbow(TestCase): - - def test_build_rainbow(self): - docker_client = docker.client.from_env() - image_names = ['my_static_input_task_image', 'my_task_output_input_task_image'] - - for image_name in image_names: - if len(docker_client.images.list(image_name)) > 0: - docker_client.images.remove(image=image_name) - - build_rainbows.build_rainbows('tests/runners/airflow/rainbow') - - for image_name in image_names: - docker_client.images.get(name=image_name) - - docker_client.close() - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/runners/airflow/build/test_build_rainbows.py b/tests/runners/airflow/build/test_build_rainbows.py new file mode 100644 index 0000000..9a4d31c --- /dev/null +++ b/tests/runners/airflow/build/test_build_rainbows.py @@ -0,0 +1,56 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import unittest +from unittest import TestCase + +import docker + +from rainbow.build import build_rainbows + + +class TestBuildRainbows(TestCase): + + __image_names = [ + 'my_static_input_task_image', + 'my_task_output_input_task_image', + 'my_server_image' + ] + + def setUp(self) -> None: + self.docker_client = docker.client.from_env() + self.__remove_images() + + def tearDown(self) -> None: + self.__remove_images() + self.docker_client.close() + + def __remove_images(self): + for image_name in self.__image_names: + if len(self.docker_client.images.list(image_name)) > 0: + self.docker_client.images.remove(image=image_name) + + def test_build_rainbow(self): + build_rainbows.build_rainbows('tests/runners/airflow/rainbow') + + for image in self.__image_names: + self.docker_client.images.get(image) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/tests/runners/airflow/rainbow/helloworld/__init__.py similarity index 100% copy from tests/runners/airflow/rainbow/hello_world/__init__.py copy to tests/runners/airflow/rainbow/helloworld/__init__.py diff --git a/tests/runners/airflow/rainbow/hello_world/hello_world.py b/tests/runners/airflow/rainbow/helloworld/hello_world.py similarity index 100% rename from tests/runners/airflow/rainbow/hello_world/hello_world.py rename to tests/runners/airflow/rainbow/helloworld/hello_world.py diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/tests/runners/airflow/rainbow/myserver/__init__.py similarity index 100% copy from tests/runners/airflow/rainbow/hello_world/__init__.py copy to tests/runners/airflow/rainbow/myserver/__init__.py diff --git a/tests/runners/airflow/rainbow/hello_world/__init__.py b/tests/runners/airflow/rainbow/myserver/my_server.py similarity index 95% rename from tests/runners/airflow/rainbow/hello_world/__init__.py rename to tests/runners/airflow/rainbow/myserver/my_server.py index 217e5db..a3f0f2c 100644 --- a/tests/runners/airflow/rainbow/hello_world/__init__.py +++ b/tests/runners/airflow/rainbow/myserver/my_server.py @@ -15,3 +15,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + + +def myendpoint1func(): + return '1' diff --git a/tests/runners/airflow/rainbow/rainbow.yml b/tests/runners/airflow/rainbow/rainbow.yml index 2000621..e9f9045 100644 --- a/tests/runners/airflow/rainbow/rainbow.yml +++ b/tests/runners/airflow/rainbow/rainbow.yml @@ -29,14 +29,14 @@ pipelines: type: python description: static input task image: my_static_input_task_image - source: hello_world + source: helloworld env_vars: env1: "a" env2: "b" input_type: static input_path: '[ { "foo": "bar" }, { "foo": "baz" } ]' output_path: /output.json - cmd: python hello_world.py + cmd: python -u helloworld.py # - task: my_parallelized_static_input_task # type: python # description: parallelized static input task @@ -48,31 +48,26 @@ pipelines: # input_path: '[ { "foo": "bar" }, { "foo": "baz" } ]' # split_input: True # executors: 2 -# cmd: python hello_world.py +# cmd: python -u helloworld.py - task: my_task_output_input_task type: python description: parallelized static input task image: my_task_output_input_task_image - source: hello_world + source: helloworld env_vars: env1: "a" env2: "b" input_type: task input_path: my_static_input_task - cmd: python hello_world.py + cmd: python -u helloworld.py services: - service: - name: myserver1 - type: python-server + name: my_python_server + type: python_server description: my python server - artifact-id: myserver1artifactid - source: myserver1logicfolder + image: my_server_image + source: myserver endpoints: - - endpoint: - path: /myendpoint1 - module: mymodule1 - function: myfun1 - - endpoint: - path: /myendpoint2 - module: mymodule2 - function: myfun2 + - endpoint: /myendpoint1 + module: myserver.my_server + function: myendpoint1func diff --git a/tests/runners/airflow/tasks/test_python.py b/tests/runners/airflow/tasks/test_python.py index 260f71d..18e6c1a 100644 --- a/tests/runners/airflow/tasks/test_python.py +++ b/tests/runners/airflow/tasks/test_python.py @@ -50,7 +50,7 @@ class TestPythonTask(TestCase): 'task': task_id, 'cmd': 'foo bar', 'image': 'rainbow_image', - 'source': 'tests/runners/airflow/rainbow/hello_world', + 'source': 'tests/runners/airflow/rainbow/helloworld', 'input_type': 'my_input_type', 'input_path': 'my_input', 'output_path': '/my_output.json'
