Search code examples
pythoncommand-line-interfacepython-click

AttributeError: 'function' object has no attribute 'name' when using Click to create Hierarchical command groups


I am trying to create a command line application with hierarchical commands (my sub-commands will have sub-commands). However, when I attempt a very basic application, I get an AttributeError.

I am able to replicate this with a simple example.

Directory layout:

.
├── cli.py
└── commands
    ├── config_cmds.py
    ├── __init__.py

cli.py

# -*- coding: utf-8 -*-

import sys
import click


from commands.config_cmds import configcmd

@click.group()
@click.version_option()
def cli(args=None):
    """A command line application"""
    return 0

cli.add_command(configcmd)


if __name__ == "__main__":
    sys.exit(cli())  # pragma: no cover

config_cmds.py

import click

@click.group
@click.version_option()
def configcmd():
    """Configuration management for this CLI"""
    click.echo("In config")

If I run this application, I get the following error:

$ python cli.py
Traceback (most recent call last):
  File "cli.py", line 15, in <module>
    cli.add_command(configcmd)
  File "/home/frank/.virtualenvs/clitest/lib/python3.6/site-packages/click/core.py", line 1221, in add_command
    name = name or cmd.name
AttributeError: 'function' object has no attribute 'name'

My directory structure is set up based on this answer.

I am using python 3.6 and Click version 7.0.

How do I resolve this attribute error so that I can have a hierarchy of commands and keep the commands split into multiple files?


Solution

  • You need to call the click.group() decorator like:

    @click.group()
    @click.version_option()
    def configcmd():
        """Configuration management for this CLI"""
        click.echo("In config")
    

    Test Code:

    import sys
    import click
    
    from commands.config_cmd import configcmd
    
    
    @click.group()
    @click.version_option()
    def cli(args=None):
        """A command line application"""
        return 0
    
    
    cli.add_command(configcmd)
    
    
    @configcmd.command()
    def test_cmd():
        click.echo('In test_cmd')
    
    
    if __name__ == "__main__":
        commands = (
            'configcmd test_cmd',
            'configcmd --help',
            '--help',
            '',
        )
    
        import sys, time
    
        time.sleep(1)
        print('Click Version: {}'.format(click.__version__))
        print('Python Version: {}'.format(sys.version))
        for cmd in commands:
            try:
                time.sleep(0.1)
                print('-----------')
                print('> ' + cmd)
                time.sleep(0.1)
                cli(cmd.split())
    
            except BaseException as exc:
                if str(exc) != '0' and \
                        not isinstance(exc, (click.ClickException, SystemExit)):
                    raise
    

    Results:

    Click Version: 6.7
    Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
    -----------
    > configcmd test_cmd
    In config
    In test_cmd
    -----------
    > configcmd --help
    Usage: test.py configcmd [OPTIONS] COMMAND [ARGS]...
    
      Configuration management for this CLI
    
    Options:
      --version  Show the version and exit.
      --help     Show this message and exit.
    
    Commands:
      test_cmd
    -----------
    > --help
    Usage: test.py [OPTIONS] COMMAND [ARGS]...
    
      A command line application
    
    Options:
      --version  Show the version and exit.
      --help     Show this message and exit.
    
    Commands:
      configcmd  Configuration management for this CLI
    -----------
    > 
    Usage: test.py [OPTIONS] COMMAND [ARGS]...
    
      A command line application
    
    Options:
      --version  Show the version and exit.
      --help     Show this message and exit.
    
    Commands:
      configcmd  Configuration management for this CLI