diff --git a/lower-bound-requirements.txt b/lower-bound-requirements.txt index 8e9232ec37..93d912410a 100644 --- a/lower-bound-requirements.txt +++ b/lower-bound-requirements.txt @@ -1,6 +1,6 @@ # core scipy==1.1.0 -click==7.0 +typer==0.0.2 tqdm==4.56.0 jsonschema==3.0.0 jsonpatch==1.15 diff --git a/setup.cfg b/setup.cfg index 9b399beb30..0e1154efba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ include_package_data = True python_requires = >=3.7 install_requires = scipy>=1.1.0 # requires numpy, which is required by pyhf and tensorflow - click>=7.0 # for console scripts + typer>=0.0.2 # for console scripts tqdm>=4.56.0 # for readxml jsonschema>=3.0.0 # for utils jsonpatch>=1.15 diff --git a/setup.py b/setup.py index 4773d6ffdf..938edf5585 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ from setuptools import setup extras_require = { - 'shellcomplete': ['click_completion'], 'tensorflow': [ 'tensorflow~=2.3,!=2.3.0', # c.f. https://github.com/tensorflow/tensorflow/pull/40789 'tensorflow-probability~=0.11', @@ -27,7 +26,6 @@ extras_require['backends'] + extras_require['xmlio'] + extras_require['contrib'] - + extras_require['shellcomplete'] + [ 'pytest~=6.0', 'pytest-cov>=2.5.1', diff --git a/src/pyhf/cli/__init__.py b/src/pyhf/cli/__init__.py index 0d65039661..bc9b1fceda 100644 --- a/src/pyhf/cli/__init__.py +++ b/src/pyhf/cli/__init__.py @@ -1,12 +1,12 @@ """The pyhf command line interface.""" -from pyhf.cli.cli import pyhf as cli +# from pyhf.cli.cli import pyhf as cli +from pyhf.cli.cli import typer_click_object as cli from pyhf.cli.rootio import cli as rootio from pyhf.cli.spec import cli as spec from pyhf.cli.infer import cli as infer -from pyhf.cli.complete import cli as complete from pyhf.contrib import cli as contrib -__all__ = ['cli', 'rootio', 'spec', 'infer', 'complete', 'contrib'] +__all__ = ['cli', 'rootio', 'spec', 'infer', 'contrib'] def __dir__(): diff --git a/src/pyhf/cli/cli.py b/src/pyhf/cli/cli.py index a1a486fe54..0e54c2a8f3 100644 --- a/src/pyhf/cli/cli.py +++ b/src/pyhf/cli/cli.py @@ -1,58 +1,91 @@ """The pyhf Command Line Interface.""" import logging +from typing import Optional import click +import typer from pyhf import __version__ -from pyhf.cli import rootio, spec, infer, patchset, complete +from pyhf.cli import rootio, spec, infer, patchset from pyhf.contrib import cli as contrib from pyhf import utils logging.basicConfig() log = logging.getLogger(__name__) +app = typer.Typer() -def _print_citation(ctx, param, value): + +# REMOVE THIS. CURRENTLY IN AS EXAMPLE +@app.command() +def top(): + """ + Top level command, form Typer + """ + typer.echo("The Typer app is at the top level") + + +def _version_callback(value: bool): + if value: + typer.echo(f"pyhf, v{__version__}") + raise typer.Exit() + + +def _print_citation(ctx, value): if not value or ctx.resilient_parsing: return - click.echo(utils.citation()) - ctx.exit() + typer.echo(utils.citation()) + raise typer.Exit() + + +@app.callback() +def callback( + version: Optional[bool] = typer.Option( + None, "--version", callback=_version_callback, is_eager=True + ), + citation: Optional[bool] = typer.Option( + None, + "--cite", + "--citation", + help="Print the BibTeX citation for this software.", + callback=_print_citation, + is_eager=True, + is_flag=True, # Needed? + expose_value=False, # Needed? + ), +): + """ + Typer app, including Click subapp + + Top-level CLI entrypoint. + """ @click.group(context_settings=dict(help_option_names=['-h', '--help'])) -@click.version_option(version=__version__) -@click.option( - "--cite", - "--citation", - help="Print the bibtex citation for this software", - default=False, - is_flag=True, - callback=_print_citation, - expose_value=False, - is_eager=True, -) def pyhf(): """Top-level CLI entrypoint.""" -# pyhf.add_command(rootio.cli) -pyhf.add_command(rootio.json2xml) -pyhf.add_command(rootio.xml2json) +typer_click_object = typer.main.get_command(app) + +typer_click_object.add_command(pyhf) -# pyhf.add_command(spec.cli) -pyhf.add_command(spec.inspect) -pyhf.add_command(spec.prune) -pyhf.add_command(spec.rename) -pyhf.add_command(spec.combine) -pyhf.add_command(spec.digest) -pyhf.add_command(spec.sort) +# typer_click_object.add_command(rootio.cli) +typer_click_object.add_command(rootio.json2xml) +typer_click_object.add_command(rootio.xml2json) -# pyhf.add_command(infer.cli) -pyhf.add_command(infer.fit) -pyhf.add_command(infer.cls) +# typer_click_object.add_command(spec.cli) +typer_click_object.add_command(spec.inspect) +typer_click_object.add_command(spec.prune) +typer_click_object.add_command(spec.rename) +typer_click_object.add_command(spec.combine) +typer_click_object.add_command(spec.digest) +typer_click_object.add_command(spec.sort) -pyhf.add_command(patchset.cli) +# typer_click_object.add_command(infer.cli) +typer_click_object.add_command(infer.fit) +typer_click_object.add_command(infer.cls) -pyhf.add_command(complete.cli) +typer_click_object.add_command(patchset.cli) -pyhf.add_command(contrib.cli) +typer_click_object.add_command(contrib.cli) diff --git a/src/pyhf/cli/complete.py b/src/pyhf/cli/complete.py deleted file mode 100644 index 8864c11388..0000000000 --- a/src/pyhf/cli/complete.py +++ /dev/null @@ -1,30 +0,0 @@ -'''Shell completions for pyhf.''' -import click - -try: - import click_completion - - click_completion.init() - - @click.command(help='Generate shell completion code.', name='completions') - @click.argument( - 'shell', - required=False, - type=click_completion.DocumentedChoice(click_completion.core.shells), - ) - def cli(shell): - '''Generate shell completion code for various shells.''' - click.echo(click_completion.core.get_code(shell, prog_name='pyhf')) - - -except ImportError: - - @click.command(help='Generate shell completion code.', name='completions') - @click.argument('shell', default=None) - def cli(shell): - '''Placeholder for shell completion code generatioon function if necessary dependency is missing.''' - click.secho( - 'This requires the click_completion module.\n' - 'You can install it with the shellcomplete extra:\n' - 'python -m pip install pyhf[shellcomplete]' - ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8b2bda812c..51f4deba47 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,21 +1,12 @@ from click.testing import CliRunner -import sys -import importlib +# FIXME +# pyhf.cli.complete was removed given typer supports completion +# so need to rewrite and fix this def test_shllcomplete_cli(isolate_modules): from pyhf.cli.complete import cli runner = CliRunner() result = runner.invoke(cli, ['bash']) assert 'complete -F _pyhf_completion -o default pyhf' in result.output - - -def test_shllcomplete_cli_missing_extra(isolate_modules): - sys.modules['click_completion'] = None - importlib.reload(sys.modules['pyhf.cli.complete']) - from pyhf.cli.complete import cli - - runner = CliRunner() - result = runner.invoke(cli, ['bash']) - assert 'You can install it with the shellcomplete extra' in result.output diff --git a/tests/test_public_api_repr.py b/tests/test_public_api_repr.py index ccb4738d63..1e6b853f4e 100644 --- a/tests/test_public_api_repr.py +++ b/tests/test_public_api_repr.py @@ -34,7 +34,7 @@ def test_top_level_public_api(): def test_cli_public_api(): - assert dir(pyhf.cli) == ["cli", "complete", "contrib", "infer", "rootio", "spec"] + assert dir(pyhf.cli) == ["cli", "contrib", "infer", "rootio", "spec"] def test_compat_public_api():