Skip to content

[QUESTION] How to avoid explicit call to subcommands command #119

Closed
@Andrew-Sheridan

Description

@Andrew-Sheridan

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

I'm not sure how to articulate this question without an example. Because of that I had some trouble googling around.

I have two installed packages, foo and bar.

  • bar can be used independently of foo. Ex: bar PATH # bar does stuff with PATH
  • foo accepts bar as a plugin. Now foo can use the logic from bar. Ex: foo bar PATH. # foo does bar stuff with PATH
  • foo can also do other stuff that is built into itself. Ex: foo fizz PATH. # foo does fizz stuff with PATH
  • I have this working with argparse but can't get foo bar PATH working quite right with typer

So here goes with an example.

In one file bar/src/bar/cli.py I have

import typer

app = typer.Typer()


@app.command()
def main(path: str = typer.Argument(...)):
    typer.echo(f"Doing bar things to {path}")

If I call bar --help I get

Usage: bar [OPTIONS] PATH

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.

  --help                          Show this message and exit.

which is great.

This is installed with this setup.py from bar/setup.py

"""Setup."""

from setuptools import find_packages
from setuptools import setup

PACKAGE_NAME = "bar"

setup(
    name=PACKAGE_NAME,
    version="0.0.1",
    package_dir={"": "src"},
    packages=find_packages(where="src"),
    zip_safe=False,
    include_package_data=True,
    python_requires=">=3.7",
    entry_points={
        "console_scripts": [f"{PACKAGE_NAME} = {PACKAGE_NAME}.cli:app"],
        "foo.cli_plugins": [f"{PACKAGE_NAME} = {PACKAGE_NAME}.cli"],
    },
    install_requires=["typer"],
)
  • there is also an empty __init__.py in bar/src/bar/

In another file foo/src/foo/cli.py I have

import typer
import pkg_resources

app = typer.Typer()

# real program has lots of plugins
CLI_PLUGINS = {
            entry_point.name: entry_point.load()
            for entry_point in pkg_resources.iter_entry_points("foo.cli_plugins")
        }

for name, entry_point in CLI_PLUGINS.items():
    app.add_typer(entry_point.app, name=name)


@app.command()
def fizz(path: str = typer.Argument(...)):
    typer.echo(f"Fizzy {path}")

This is installed with this setup.py from foo/setup.py

"""Setup."""

from pathlib import Path

from setuptools import find_packages
from setuptools import setup

PACKAGE_NAME = "foo"

setup(
    name=PACKAGE_NAME,
    version="0.0.1",
    package_dir={"": "src"},
    packages=find_packages(where="src"),
    zip_safe=False,
    include_package_data=True,
    python_requires=">=3.7",
    entry_points={
        "console_scripts": [f"{PACKAGE_NAME} = {PACKAGE_NAME}.cli:app"],
    },
    install_requires=["typer"],
)
  • there is also an empty __init__.py in foo/src/foo/

When I call foo --help I get

Usage: ex-primary [OPTIONS] COMMAND [ARGS]...

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.

  --help                          Show this message and exit.

Commands:
  fizz
  bar

which is also great. This is exactly what I want.

However..

When I call foo bar --help I get

Usage: foo bar [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  main

Which is not what I want. I don't want to have to call main out by name here.

Question

  • Is there a way to not have main as a command here? I want to be able to call foo bar PATH instead of foo bar main PATH.

What I want for for bar --help is

Usage: foo bar [OPTIONS] PATH

Options:
  --install-completion [bash|zsh|fish|powershell|pwsh]
                                  Install completion for the specified shell.
  --show-completion [bash|zsh|fish|powershell|pwsh]
                                  Show completion for the specified shell, to
                                  copy it or customize the installation.

  --help                          Show this message and exit.

Additional context

I have tried adding a callback to the add_typer and it almost does what I want.
Ex:
changing

app.add_typer(entry_point.app, name=name)

to

app.add_typer(entry_point.app, name=name, callback=entry_point.main) 
  • Doing this adds the argument of bar to the usage of foo bar --help (yay) but it still leaves the COMMAND [ARGS] and the Commands section. Also I don't think this is the right way to go about this.

I feel like there is a nice way to get this working and I just cannot see it.

I looked at the documentation (which is beautiful) and the example with subcommands doesn't quite match this use case. There the subcommands are designed to be called explicitly.

If there is any more information I can provide please let me know.

Thank you for your great work on typer! Cheers

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions