Skip to content

Decouple command line argument processing from handling. #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 15 additions & 95 deletions unity_app_generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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="?",
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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()
4 changes: 3 additions & 1 deletion unity_app_generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
102 changes: 102 additions & 0 deletions unity_app_generator/interface.py
Original file line number Diff line number Diff line change
@@ -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