From 5c92f66697297baf35bd3a17b8f7f2356db1dad6 Mon Sep 17 00:00:00 2001 From: James McDuffie Date: Tue, 25 Feb 2025 15:25:13 -0800 Subject: [PATCH] Decouple command line argument processing from handling. Add interface layer that mimics command line interace that can be called programmtically. --- unity_app_generator/__main__.py | 110 +++++-------------------------- unity_app_generator/generator.py | 4 +- unity_app_generator/interface.py | 102 ++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 96 deletions(-) create mode 100644 unity_app_generator/interface.py diff --git a/unity_app_generator/__main__.py b/unity_app_generator/__main__.py index a20b2be..18a46e9 100755 --- a/unity_app_generator/__main__.py +++ b/unity_app_generator/__main__.py @@ -13,97 +13,20 @@ # * Creates CWL files from application metadata and Docker registry URL # * Register application and pushes CWL files into Dockstore -STATE_DIRECTORY = ".unity_app_gen" - -import os import logging from argparse import ArgumentParser -from unity_app_generator.generator import UnityApplicationGenerator, ApplicationGenerationError - -logger = logging.getLogger() - -class SubCommandError(Exception): - pass - -def state_directory_path(args): - - if args.state_directory is not None: - return os.path.realpath(args.state_directory) - - if hasattr(args, "destination_directory") and args.destination_directory is not None: - return os.path.realpath(os.path.join(args.destination_directory, STATE_DIRECTORY)) - - if hasattr(args, "source_repository") and os.path.isdir(args.source_repository): - return os.path.realpath(os.path.join(args.source_repository, STATE_DIRECTORY)) - - return os.path.realpath(os.path.join(os.curdir, STATE_DIRECTORY)) - -def check_state_directory(state_dir): - - if not os.path.exists(state_dir): - raise SubCommandError(f"Application state directory {state_dir} does not exist, please run init sub-command first") - - return state_dir - -def init(args): - state_dir = state_directory_path(args) - - app_gen = UnityApplicationGenerator(state_dir, args.source_repository, args.destination_directory, args.checkout) - -def build_docker(args): - state_dir = check_state_directory(state_directory_path(args)) - - app_gen = UnityApplicationGenerator(state_dir, - repo2docker_config=args.config_file, - use_namespace=args.image_namespace, - use_repository=args.image_repository, - use_tag=args.image_tag) - - app_gen.create_docker_image() +from unity_app_generator.generator import ApplicationGenerationError -def push_docker(args): - state_dir = check_state_directory(state_directory_path(args)) +from . import interface - app_gen = UnityApplicationGenerator(state_dir) - - app_gen.push_to_docker_registry(args.container_registry) - -def push_ecr(args): - state_dir = check_state_directory(state_directory_path(args)) - - app_gen = UnityApplicationGenerator(state_dir) - - app_gen.push_to_aws_ecr() - -def notebook_parameters(args): - - state_dir = check_state_directory(state_directory_path(args)) - - app_gen = UnityApplicationGenerator(state_dir) - - print() - print(app_gen.notebook_parameters()) - -def build_cwl(args): - state_dir = check_state_directory(state_directory_path(args)) - - app_gen = UnityApplicationGenerator(state_dir) - - app_gen.create_cwl(cwl_output_path=args.cwl_output_path, docker_url=args.image_url, monolithic=args.monolithic) - -def push_app_registry(args): - state_dir = check_state_directory(state_directory_path(args)) - - app_gen = UnityApplicationGenerator(state_dir) - - app_gen.push_to_application_registry(args.dockstore_api_url, args.dockstore_token) +logger = logging.getLogger() def main(): parser = ArgumentParser(description="Unity Application Package Generator") parser.add_argument("--state_directory", - help=f"An alternative location to store the application state other than {STATE_DIRECTORY}") + help=f"An alternative location to store the application state other than {interface.DEFAULT_STATE_DIRECTORY}") parser.add_argument("--verbose", "-v", action="store_true", default=False, help=f"Enable verbose logging") @@ -112,9 +35,9 @@ def main(): subparsers = parser.add_subparsers(required=True) parser_init = subparsers.add_parser('init', - help=f"Initialize a Git repository for use by this application. Creates a {STATE_DIRECTORY} directory in the destination directory") + help=f"Initialize a Git repository for use by this application. Creates a {interface.DEFAULT_STATE_DIRECTORY} directory in the destination directory") - parser_init.add_argument("source_repository", + parser_init.add_argument("source_repository", help="Directory or Git URL of application source files, default is current directory") parser_init.add_argument("destination_directory", nargs="?", @@ -123,7 +46,7 @@ def main(): parser_init.add_argument("-c", "--checkout", required=False, help="Git hash, tag or branch to checkout from the source repository") - parser_init.set_defaults(func=init) + parser_init.set_defaults(func=interface.init) # build_docker @@ -142,7 +65,7 @@ def main(): parser_build_docker.add_argument("-c", "--config_file", help="JSON or Python Traitlets style config file for repo2docker. Use 'repo2docker --help-all' to see configurable options.") - parser_build_docker.set_defaults(func=build_docker) + parser_build_docker.set_defaults(func=interface.build_docker) # push_docker @@ -152,21 +75,21 @@ def main(): parser_push_docker.add_argument("container_registry", help="URL or Dockerhub username of a Docker registry for pushing of the built image") - parser_push_docker.set_defaults(func=push_docker) + parser_push_docker.set_defaults(func=interface.push_docker) # push_ecr parser_push_ecr = subparsers.add_parser('push_ecr', help=f"Push a Docker image from the initialized application directory to an AWS Elastic Container Registry (ECR)") - parser_push_ecr.set_defaults(func=push_ecr) + parser_push_ecr.set_defaults(func=interface.push_ecr) # notebook_parameters parser_parameters = subparsers.add_parser('parameters', help=f"Display parsed notebook parameters") - parser_parameters.set_defaults(func=notebook_parameters) + parser_parameters.set_defaults(func=interface.notebook_parameters) # build_cwl @@ -182,7 +105,7 @@ def main(): parser_build_cwl.add_argument("--monolithic", action="store_true", help="Use the deprecated 'monolithic' approach to generating CWL where stage in and out are bundled inside the application") - parser_build_cwl.set_defaults(func=build_cwl) + parser_build_cwl.set_defaults(func=interface.build_cwl) # push_app_registry @@ -195,7 +118,7 @@ def main(): parser_app_registry.add_argument("--token", dest="dockstore_token", required=True, help="Dockstore API token obtained from the My Services / Account page") - parser_app_registry.set_defaults(func=push_app_registry) + parser_app_registry.set_defaults(func=interface.push_app_registry) # Process arguments @@ -207,12 +130,9 @@ def main(): logging.basicConfig(level=logging.INFO) try: - args.func(args) - except (SubCommandError, ApplicationGenerationError) as err: + args.func(**vars(args)) + except ApplicationGenerationError as err: parser.error(err) - - #app_gen.push_to_application_registry(None) - if __name__ == '__main__': main() \ No newline at end of file diff --git a/unity_app_generator/generator.py b/unity_app_generator/generator.py index 90c7f0e..372c934 100644 --- a/unity_app_generator/generator.py +++ b/unity_app_generator/generator.py @@ -147,7 +147,9 @@ def create_cwl(self, cwl_output_path=None, docker_url=None, monolithic=False): def notebook_parameters(self): - nb = ApplicationNotebook(self.repo_info) + notebook_filename = os.path.join(self.repo_info.directory, "process.ipynb") + + nb = ApplicationNotebook(notebook_filename) params_str = "Parsed Notebook Parameters:\n" params_str += nb.parameter_summary() diff --git a/unity_app_generator/interface.py b/unity_app_generator/interface.py new file mode 100644 index 0000000..cc14b5b --- /dev/null +++ b/unity_app_generator/interface.py @@ -0,0 +1,102 @@ +""" +Provides a programmatic interface mirroring the command line interface of build_ogc_app +""" + +DEFAULT_STATE_DIRECTORY = ".unity_app_gen" + +import os +import logging + +from unity_app_generator.generator import UnityApplicationGenerator, ApplicationGenerationError + +logger = logging.getLogger() + +# Defaulty name of place where application generation state data is kept +DEFAULT_STATE_DIRECTORY = ".unity_app_gen" + +def state_directory_path(state_directory=None): + "Resolve a path to the state directory based on which arguments are provided" + + if state_directory is not None: + return os.path.realpath(state_directory) + + return os.path.realpath(os.path.join(os.curdir, DEFAULT_STATE_DIRECTORY)) + +def check_state_directory(state_dir): + "Check that the application state directory exists" + + if not os.path.exists(state_dir): + raise ApplicationGenerationError(f"Application state directory {state_dir} does not exist, please run init sub-command first") + + return state_dir + +def init(state_directory, source_repository, destination_directory=None, checkout=None, **kwargs): + "Initialize a Git repository for use by subsequent commands" + + state_dir = state_directory_path(state_directory) + + app_gen = UnityApplicationGenerator(state_dir, source_repository, destination_directory, checkout) + + return app_gen + +def build_docker(state_directory, image_namespace=None, image_repository=None, image_tag=None, config_file=None, **kwargs): + "Build a Docker image from the initialized application directory" + + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir, + repo2docker_config=config_file, + use_namespace=image_namespace, + use_repository=image_repository, + use_tag=image_tag) + + app_gen.create_docker_image() + + return app_gen + +def push_docker(state_directory, container_registry, **kwargs): + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir) + + app_gen.push_to_docker_registry(container_registry) + + return app_gen + +def push_ecr(state_directory, **kwargs): + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir) + + app_gen.push_to_aws_ecr() + + return app_gen + +def notebook_parameters(state_directory, **kwargs): + + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir) + + print() + print(app_gen.notebook_parameters()) + + return app_gen + +def build_cwl(state_directory, cwl_output_path=None, image_url=None, monolithic=False, **kwargs): + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir) + + app_gen.create_cwl(cwl_output_path=cwl_output_path, docker_url=image_url, monolithic=monolithic) + + return app_gen + +def push_app_registry(state_directory, dockstore_api_url, dockstore_token, **kwargs): + state_dir = check_state_directory(state_directory_path(state_directory)) + + app_gen = UnityApplicationGenerator(state_dir) + + app_gen.push_to_application_registry(dockstore_api_url, dockstore_token) + + return app_gen \ No newline at end of file