diff --git a/.pylintrc b/.pylintrc index 8b6cb76..f9ece53 100644 --- a/.pylintrc +++ b/.pylintrc @@ -152,7 +152,7 @@ ignore-comments=yes ignore-docstrings=yes # Ignore imports when computing similarities. -ignore-imports=no +ignore-imports=yes [TYPECHECK] diff --git a/metagenscope_cli/cli/__init__.py b/metagenscope_cli/cli/__init__.py index e4e3859..b1eec93 100644 --- a/metagenscope_cli/cli/__init__.py +++ b/metagenscope_cli/cli/__init__.py @@ -1,6 +1,9 @@ """MetaGenScope CLI.""" import click +import click_log + +from metagenscope_cli.extensions import logger from .auth_cli import register, login, status from .get_cli import get @@ -8,6 +11,9 @@ from .upload_cli import upload +click_log.basic_config(logger) + + @click.group() def main(): """Use to interact with the MetaGenScope web platform.""" diff --git a/metagenscope_cli/cli/auth_cli.py b/metagenscope_cli/cli/auth_cli.py index 3865bae..e3a77f3 100644 --- a/metagenscope_cli/cli/auth_cli.py +++ b/metagenscope_cli/cli/auth_cli.py @@ -2,32 +2,37 @@ import os import click +import click_log from requests.exceptions import HTTPError +from metagenscope_cli.extensions import logger from metagenscope_cli.network.authenticator import Authenticator from metagenscope_cli.config import config from .utils import add_authorization -def handle_auth_request(request_generator): +def handle_auth_request(request_generator, save_token_silently): """Perform common authentication request functions.""" try: jwt_token = request_generator() - click.echo(f'JWT Token: {jwt_token}') + logger.info(f'JWT Token: {jwt_token}') - if click.confirm('Store token for future use (overwrites existing)?'): + save_message = 'Store token for future use (overwrites existing)?' + if save_token_silently or click.confirm(save_message): config.set_token(jwt_token) except HTTPError as http_error: - click.echo(f'There was an error with registration: {http_error}', err=True) + logger.error(f'There was an error with registration: {http_error}') @click.command() +@click_log.simple_verbosity_option(logger) @click.option('-h', '--host', default=None) +@click.option('-y', '--save-token-silently', default=False) @click.argument('username') @click.argument('user_email') @click.argument('password') -def register(host, username, user_email, password): +def register(host, save_token_silently, username, user_email, password): """Register as a new MetaGenScope user.""" if host is None: host = os.environ['MGS_HOST'] @@ -37,14 +42,16 @@ def request_generator(): """Generate registration auth request.""" return authenticator.register(username, user_email, password) - handle_auth_request(request_generator) + handle_auth_request(request_generator, save_token_silently) @click.command() +@click_log.simple_verbosity_option(logger) @click.option('-h', '--host', default=None) +@click.option('-y', '--save-token-silently', default=False) @click.argument('user_email') @click.argument('password') -def login(host, user_email, password): +def login(host, save_token_silently, user_email, password): """Authenticate as an existing MetaGenScope user.""" if host is None: host = os.environ['MGS_HOST'] @@ -54,12 +61,13 @@ def request_generator(): """Generate registration auth request.""" return authenticator.login(user_email, password) - handle_auth_request(request_generator) + handle_auth_request(request_generator, save_token_silently) @click.command() +@click_log.simple_verbosity_option(logger) @add_authorization() def status(uploader): """Get user status.""" response = uploader.knex.get('/api/v1/auth/status') - click.echo(response) + logger.info(response) diff --git a/metagenscope_cli/cli/get_cli.py b/metagenscope_cli/cli/get_cli.py index c3c5f51..533a127 100644 --- a/metagenscope_cli/cli/get_cli.py +++ b/metagenscope_cli/cli/get_cli.py @@ -1,6 +1,9 @@ """CLI to get data from a MetaGenScope Server.""" import click +import click_log + +from metagenscope_cli.extensions import logger from .utils import add_authorization @@ -19,10 +22,11 @@ def uuids(): def report_uuid(name, uuid): """Report a uuid to the user.""" - click.echo(f'{name}\t{uuid}') + logger.info(f'{name}\t{uuid}') @uuids.command(name='samples') +@click_log.simple_verbosity_option(logger) @add_authorization() @click.argument('sample_names', nargs=-1) def sample_uuids(uploader, sample_names): @@ -34,6 +38,7 @@ def sample_uuids(uploader, sample_names): @uuids.command(name='groups') +@click_log.simple_verbosity_option(logger) @add_authorization() @click.argument('sample_group_names', nargs=-1) def sample_group_uuids(uploader, sample_group_names): diff --git a/metagenscope_cli/cli/run_cli.py b/metagenscope_cli/cli/run_cli.py index 9d640c0..b74cb99 100644 --- a/metagenscope_cli/cli/run_cli.py +++ b/metagenscope_cli/cli/run_cli.py @@ -1,7 +1,9 @@ """CLI to run commands on MGS server.""" -from sys import stderr import click +import click_log + +from metagenscope_cli.extensions import logger from .utils import add_authorization @@ -19,21 +21,23 @@ def middleware(): @middleware.command(name='group') +@click_log.simple_verbosity_option(logger) @add_authorization() @click.argument('group_uuid') def group_middleware(uploader, group_uuid): """Run middleware for a group.""" response = uploader.knex.post(f'/api/v1/sample_groups/{group_uuid}/middleware', {}) - click.echo(response) + logger.info(response) @middleware.command(name='sample') +@click_log.simple_verbosity_option(logger) @add_authorization() @click.argument('sample_name') def sample_middleware(uploader, sample_name): """Run middleware for a sample.""" response = uploader.knex.get(f'/api/v1/samples/getid/{sample_name}') sample_uuid = response['data']['sample_uuid'] - print(f'{sample_name} :: {sample_uuid}', file=stderr) + logger.info(f'{sample_name} :: {sample_uuid}') response = uploader.knex.post(f'/api/v1/samples/{sample_uuid}/middleware', {}) - click.echo(response) + logger.info(response) diff --git a/metagenscope_cli/cli/upload_cli.py b/metagenscope_cli/cli/upload_cli.py index 22d6bf0..f23026d 100644 --- a/metagenscope_cli/cli/upload_cli.py +++ b/metagenscope_cli/cli/upload_cli.py @@ -1,8 +1,9 @@ """CLI to upload data to a MetaGenScope Server.""" -from sys import stderr import click +import click_log +from metagenscope_cli.extensions import logger from metagenscope_cli.sample_sources.data_super_source import DataSuperSource from metagenscope_cli.sample_sources.file_source import FileSource @@ -16,6 +17,7 @@ def upload(): @upload.command() +@click_log.simple_verbosity_option(logger) @add_authorization() @click.argument('metadata_csv') @click.argument('sample_names', nargs=-1) @@ -29,12 +31,13 @@ def metadata(uploader, metadata_csv, sample_names): } try: response = uploader.knex.post('/api/v1/samples/metadata', payload) - click.echo(response) + logger.info(response) except Exception: # pylint:disable=broad-except - print(f'[upload-metadata-error] {sample_name}', file=stderr) + logger.error(f'[upload-metadata-error] {sample_name}') @upload.command() +@click_log.simple_verbosity_option(logger) @add_authorization() @click.option('-g', '--group', default=None) @click.option('--group-name', default=None) @@ -47,6 +50,7 @@ def datasuper(uploader, group, group_name): @upload.command() +@click_log.simple_verbosity_option(logger) @add_authorization() @click.option('-g', '--group', default=None) @click.argument('result_files', nargs=-1) diff --git a/metagenscope_cli/cli/utils.py b/metagenscope_cli/cli/utils.py index 0189d45..24d690b 100644 --- a/metagenscope_cli/cli/utils.py +++ b/metagenscope_cli/cli/utils.py @@ -1,13 +1,13 @@ """Utilities for MetaGenScope CLI.""" import os -from sys import stderr from datetime import datetime from functools import wraps import click from requests.exceptions import HTTPError +from metagenscope_cli.extensions import logger from metagenscope_cli.network import Knex, Uploader from metagenscope_cli.network.token_auth import TokenAuth from metagenscope_cli.tools.parse_metadata import parse_metadata_from_csv @@ -22,10 +22,10 @@ def parse_metadata(filename, sample_names): def warn_missing_auth(): """Warn user of missing authentication.""" - click.echo('No authenication means provided!', err=True) - click.echo('You must provide an authentication means either by passing ' - '--auth-token or by persisting a login token to your local ' - 'MetaGenScope configuration file (see metagenscope login help).') + logger.error('No authenication means provided!') + logger.error('You must provide an authentication means either by passing ' + '--auth-token or by persisting a login token to your local ' + 'MetaGenScope configuration file (see metagenscope login help).') def batch_upload(uploader, samples, group_uuid=None, upload_group_name=None): @@ -35,16 +35,16 @@ def batch_upload(uploader, samples, group_uuid=None, upload_group_name=None): if upload_group_name is None: upload_group_name = f'upload_group_{current_time}' group_uuid = uploader.create_sample_group(upload_group_name) - click.echo(f'group created: ') + logger.info(f'group created: ') try: results = uploader.upload_all_results(group_uuid, samples) except HTTPError as error: - click.echo('Could not create Sample', err=True) - click.echo(error, err=True) + logger.error('Could not create Sample') + logger.error(error) if results: - click.echo('Upload results:') + logger.info('Upload results:') for result in results: sample_uuid = result['sample_uuid'] sample_name = result['sample_name'] @@ -52,12 +52,11 @@ def batch_upload(uploader, samples, group_uuid=None, upload_group_name=None): if result['type'] == 'error': exception = result['exception'] - click.secho(f' - {sample_name} ({sample_uuid}): {result_type}', - fg='red', err=True) - click.secho(f' {exception}', fg='red', err=True) + logger.error(f' - {sample_name} ({sample_uuid}): {result_type}') + logger.error(f' {exception}') else: - click.secho(f' - {sample_name} ({sample_uuid}): {result_type}', fg='green') - click.echo(f'group info: ') + logger.info(f' - {sample_name} ({sample_uuid}): {result_type}') + logger.info(f'group info: ') def add_authorization(): @@ -78,7 +77,7 @@ def wrapper(host, auth_token, *args, **kwargs): try: host = os.environ['MGS_HOST'] except KeyError: - print('No host. Exiting', file=stderr) + logger.error('No host. Exiting') exit(1) knex = Knex(token_auth=auth, host=host) diff --git a/metagenscope_cli/extensions.py b/metagenscope_cli/extensions.py new file mode 100644 index 0000000..0272331 --- /dev/null +++ b/metagenscope_cli/extensions.py @@ -0,0 +1,6 @@ +"""Extensions for python-metagenscope.""" + +import logging + + +logger = logging.getLogger(__name__) # pylint:disable=invalid-name diff --git a/metagenscope_cli/network/knex.py b/metagenscope_cli/network/knex.py index 7ef7755..e57972d 100644 --- a/metagenscope_cli/network/knex.py +++ b/metagenscope_cli/network/knex.py @@ -1,8 +1,9 @@ """Knex wraps MetaGenScope requests requiring authentication.""" -from sys import stderr import requests + from metagenscope_cli.constants import DEFAULT_HOST +from metagenscope_cli.extensions import logger class Knex(object): @@ -31,7 +32,7 @@ def post(self, endpoint, payload): else: response = requests.post(url, headers=self.headers, auth=self.auth) if response.status_code >= 400: - print(response.content, file=stderr) + logger.error(response.content) response.raise_for_status() return response.json() diff --git a/metagenscope_cli/network/uploader.py b/metagenscope_cli/network/uploader.py index e48727e..d6dd076 100644 --- a/metagenscope_cli/network/uploader.py +++ b/metagenscope_cli/network/uploader.py @@ -2,10 +2,11 @@ from datetime import datetime from concurrent.futures import ThreadPoolExecutor -from sys import stderr from requests.exceptions import HTTPError +from metagenscope_cli.extensions import logger + class Uploader: """Uploader class handles uploading samples to a server.""" @@ -51,8 +52,8 @@ def try_upload(): """Attempt an upload, return the result.""" date_now = datetime.now() try: - print(f'[uploader {date_now}] uploading {sample_name} :: {result_type}', - file=stderr) + message = f'[uploader {date_now}] uploading {sample_name} :: {result_type}' + logger.info(message) self.upload_sample_result(sample_uuid, result_type, data, dryrun=dryrun) except Exception as exception: # pylint:disable=broad-except result['type'] = 'error' @@ -65,7 +66,7 @@ def upload_all_results(self, group_uuid, samples, dryrun=True): executor = ThreadPoolExecutor(max_workers=5) results = [] for sample_name, tool_results in samples.items(): - print(f'[uploader {datetime.now()}] creating sample {sample_name}', file=stderr) + logger.info(f'[uploader {datetime.now()}] creating sample {sample_name}') sample_uuid = self.create_sample(sample_name, group_uuid) futures = [] for tool_result in tool_results: diff --git a/metagenscope_cli/sample_sources/__init__.py b/metagenscope_cli/sample_sources/__init__.py index e9780d3..dfd5454 100644 --- a/metagenscope_cli/sample_sources/__init__.py +++ b/metagenscope_cli/sample_sources/__init__.py @@ -1,6 +1,6 @@ """Sources for sample data.""" -from sys import stderr +from metagenscope_cli.extensions import logger from metagenscope_cli.tools.parsers import parse, UnparsableError @@ -35,10 +35,10 @@ def get_sample_payloads(self): try: data = parse(result_type, files_dict) except UnparsableError: - print(f'[parse-error] could not parse {result_type}', file=stderr) + logger.error(f'[parse-error] could not parse {result_type}') continue except KeyError: - print(f'[key-error] {sample_name} :: {result_type}', file=stderr) + logger.error(f'[key-error] {sample_name} :: {result_type}') result_payload = { 'result_type': result_type, diff --git a/setup.py b/setup.py index 7b886f4..10a262f 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ dependencies = [ 'click', + 'click-log==0.2.1', 'requests', 'configparser', 'pandas',