Skip to content

Commit

Permalink
feat: migrate argparse code to typer (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe committed Oct 30, 2023
1 parent 057abd5 commit 46e7923
Show file tree
Hide file tree
Showing 14 changed files with 492 additions and 403 deletions.
3 changes: 0 additions & 3 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ jsonschema >=4.4,<4.5
# Easy logging.
logzero >=1.7.0,<2.0

# Compact, round-tripable configuration format.
toml >=0.10.2,<0.11

# simplejson has better encoders/decoders
simplejson >=3.17.2,<4.0

Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def parse_requirements(path):
"Natural Language :: English",
"Topic :: Scientific/Engineering :: Bio-Informatics",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
Expand Down
125 changes: 5 additions & 120 deletions varfish_cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,126 +1,11 @@
"""Main entry point for VarFish CLI."""
"""Formal main entry point, forwards to module ``cli``."""

import argparse
import logging
import os
import sys
from varfish_cli import cli

import logzero
from logzero import logger
import toml

from varfish_cli import __version__
from varfish_cli.case import run as run_case
from varfish_cli.case import setup_argparse as setup_argparse_case
from varfish_cli.common import CommonConfig, run_nocmd
from varfish_cli.projects import run as run_projects
from varfish_cli.projects import setup_argparse as setup_argparse_projects
from varfish_cli.varannos import run as run_varannos
from varfish_cli.varannos import setup_argparse as setup_argparse_varannos

#: Paths to search the global configuration in.
GLOBAL_CONFIG_PATHS = ("~/.varfishrc.toml",)


def setup_argparse_only(): # pragma: nocover
"""Wrapper for ``setup_argparse()`` that only returns the parser.
Only used in sphinx documentation via ``sphinx-argparse``.
"""
return setup_argparse()[0]


def setup_argparse():
"""Create argument parser."""
# Construct argument parser and set global options.
parser = argparse.ArgumentParser(prog="varfish-cli")
parser.add_argument("--verbose", action="store_true", default=False, help="Increase verbosity.")
parser.add_argument("--version", action="version", version="%%(prog)s %s" % __version__)

group = parser.add_argument_group("Basic Configuration")
group.add_argument(
"--no-verify-ssl",
dest="verify_ssl",
default=True,
action="store_false",
help="Disable HTTPS SSL verification",
)
group.add_argument(
"--config",
default=os.environ.get("VARFISH_CONFIG_PATH", None),
help="Path to configuration file.",
)
group.add_argument(
"--varfish-server-url",
default=os.environ.get("VARFISH_SERVER_URL", None),
help="VarFish server URL key to use, defaults to env VARFISH_SERVER_URL.",
)
group.add_argument(
"--varfish-api-token",
default=os.environ.get("VARFISH_API_TOKEN", None),
help="VarFish API token to use, defaults to env VARFISH_API_TOKEN.",
)

# Add sub parsers for each argument.
subparsers = parser.add_subparsers(dest="cmd")

setup_argparse_case(subparsers.add_parser("case", help="Work with cases."))
setup_argparse_projects(subparsers.add_parser("projects", help="Work with projects."))
setup_argparse_varannos(subparsers.add_parser("varannos", help="Work with varannos module."))

return parser, subparsers


def main(argv=None):
"""Main entry point before parsing command line arguments."""
# Setup command line parser.
parser, subparsers = setup_argparse()

# Actually parse command line arguments.
args = parser.parse_args(argv)

# Setup logging incl. verbosity.
if args.verbose: # pragma: no cover
level = logging.DEBUG
else:
# Remove module name and line number if not running in debug mode.s
formatter = logzero.LogFormatter(
fmt="%(color)s[%(levelname)1.1s %(asctime)s]%(end_color)s %(message)s"
)
logzero.formatter(formatter)
level = logging.INFO
logzero.loglevel(level=level)

# Load configuration, if any.
if args.config:
config_paths = (args.config,)
else:
config_paths = GLOBAL_CONFIG_PATHS
for config_path in config_paths:
config_path = os.path.expanduser(os.path.expandvars(config_path))
if os.path.exists(config_path):
with open(config_path, "rt") as tomlf:
toml_config = toml.load(tomlf)
break
else:
toml_config = None
logger.info("Could not find any of the global configuration files %s.", config_paths)

# Merge configuration from command line/environment args and configuration file.
config = CommonConfig.create(args, toml_config)

# Handle the actual command line.
cmds = {None: run_nocmd, "case": run_case, "varannos": run_varannos, "projects": run_projects}

res = cmds[args.cmd](
config, toml_config, args, parser, subparsers.choices[args.cmd] if args.cmd else None
)
if not res:
logger.info("All done. Have a nice day!")
else: # pragma: nocover
logger.error("Something did not work out correctly.")
return res
def main():
cli.typer_click_object()


if __name__ == "__main__": # pragma: no cover
sys.exit(main(sys.argv))
main()
19 changes: 19 additions & 0 deletions varfish_cli/api/project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Implementation of API operations on cases."""

import typing
import uuid

from logzero import logger
import requests
Expand All @@ -13,6 +14,8 @@

#: End point for listing projects.
ENDPOINT_PROJECT_LIST = "/project/api/list"
#: End point for retrieving projects.
ENDPOINT_PROJECT_RETRIEVE = "/project/api/retrieve/{project_uuid}"


def project_list(
Expand All @@ -28,3 +31,19 @@ def project_list(
result = requests.get(endpoint, headers=headers, verify=verify_ssl)
raise_for_status(result)
return CONVERTER.structure(result.json(), typing.List[Project])


def project_retrieve(
server_url: str,
api_token: str,
project_uuid: typing.Union[str, uuid.UUID],
verify_ssl: bool = True,
) -> typing.List[Project]:
"""Listing of all projects that a user has access to."""
server_url = strip_trailing_slash(server_url)
endpoint = f"{server_url}{ENDPOINT_PROJECT_RETRIEVE}".format(project_uuid=project_uuid)
logger.debug("Sending GET request to end point %s", endpoint)
headers = {"Authorization": "Token %s" % api_token}
result = requests.get(endpoint, headers=headers, verify=verify_ssl)
raise_for_status(result)
return CONVERTER.structure(result.json(), Project)
115 changes: 115 additions & 0 deletions varfish_cli/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Code for main entry point."""

import logging
import typing

import typer
import logzero
from logzero import logger

from varfish_cli import __version__
from varfish_cli.cli import projects, varannos
from varfish_cli.config import CommonOptions, load_config

#: Paths to search the global configuration in.
DEFAULT_PATH_VARFISHRC = "~/.varfishrc.toml"


def version_callback(value: bool):
"""Callback when called with 'version' or '--version'"""
if value:
print(f"varfish-cli {__version__}")
raise typer.Exit()


#: Main CLI ``Typer`` object.
app = typer.Typer(no_args_is_help=True)

# Register all sub commands.
app.add_typer(varannos.app, name="varannos", help="Varannos-related subcommands")
app.add_typer(projects.app, name="projects", help="Project-related subcommands")


@app.command("version")
def main_version():
"""Print version as "varfish-cli $version"."""
version_callback(True)


@app.callback()
def main(
ctx: typer.Context,
version: typing.Annotated[
typing.Optional[bool], typer.Option("--version", callback=version_callback)
] = None,
verbose: typing.Annotated[
bool, typer.Option("--verbose", "-v", help="Enable verbose output")
] = False,
verify_ssl: typing.Annotated[
bool, typer.Option("--verify-ssl/--no-verify-ssl", help="Disable SSL verification")
] = True,
config_path: typing.Annotated[
str,
typer.Option("--config-path", help="Path to configuration file", envvar="VARFISH_RC_PATH"),
] = DEFAULT_PATH_VARFISHRC,
varfish_server_url: typing.Annotated[
typing.Optional[str],
typer.Option(
"--varfish-server-url",
help=(
"VarFish server URL key to use, defaults to env VARFISH_SERVER_URL or read "
"from configfile"
),
envvar="VARFISH_SERVER_URL",
),
] = None,
varfish_api_token: typing.Annotated[
typing.Optional[str],
typer.Option(
"--varfish-server-url",
help=(
"VarFish API token to use, defaults to env VARFISH_API_TOKEN or read from "
"configfile"
),
envvar="VARFISH_API_TOKEN",
),
] = None,
):
"""Callback for main entry point
This function handles the global configuration from configuration file,
environment variables, and command line (in increasing priority).
"""
_ = version
# Setup logging
if verbose: # pragma: no cover
level = logging.DEBUG
else:
# Remove module name and line number if not running in debug mode.s
formatter = logzero.LogFormatter(
fmt="%(color)s[%(levelname)1.1s %(asctime)s]%(end_color)s %(message)s"
)
logzero.formatter(formatter)
level = logging.INFO
logzero.loglevel(level=level)

# Load configuration file
toml_varfish_server_url, toml_varfish_api_token = load_config(config_path)
if toml_varfish_server_url and not varfish_server_url:
varfish_server_url = toml_varfish_server_url
if toml_varfish_api_token and not varfish_api_token:
varfish_api_token = toml_varfish_api_token

# Construct common options
ctx.obj = CommonOptions(
verbose=verbose,
verify_ssl=verify_ssl,
config_path=config_path,
varfish_server_url=varfish_server_url,
varfish_api_token=varfish_api_token,
)
logger.info("global configuration = %s", ctx.obj.model_dump_json())


#: Define ``typer`` object that can be called from ``__main__.py``.
typer_click_object = typer.main.get_command(app)
Loading

0 comments on commit 46e7923

Please sign in to comment.