Python Click¶
Python Click is a powerful and user-friendly library for creating command-line interfaces (CLIs) in Python applications. It simplifies the process of building CLIs by providing decorators and utilities that make defining commands, options, and arguments straightforward and intuitive.
Key Features:
- Declarative Syntax: Click uses a declarative syntax to define commands, arguments, and options.
- Command Groups: Click allows you to group related commands together, creating a structured and organized CLI application.
- Input and Output Handling: Click provides utilities for reading input from users and printing output to the console.
- Options and Arguments: Click supports both command-line options (flags) and arguments (values) that your commands can accept.
- Automatic Type Conversion: Click automatically converts input values to the specified data types.
- Help Messages: Click generates help messages for your commands and options, allowing users to learn about your CLI's functionality without extra documentation.
Warning
This requires the following dependencies and has been tested at these specified versions:
Creating Basic Command-Line Commands¶
In Click, you can define a basic command using the @click.command() decorator. This decorator is used to create a function that represents a command-line command.
In our example, we're using the @click.option()'s for
--platform--ip--username--password
Create a file called network_cli.py and add the following:
import click
from napalm import get_network_driver
@click.command()
@click.option('--ip', prompt='Enter device IP address', help='Device IP address')
@click.option('--platform', prompt='Enter device platform (e.g., ios, eos)', help='Network device type')
@click.option('--username', prompt='Enter your username', help='Your username')
@click.option('--password', prompt='Enter your password', help='Your password', hide_input=True)
def get_device_facts(platform, ip, username, password):
"""Fetch network device information using Python Napalm."""
driver = get_network_driver(platform)
with driver(hostname=ip, username=username, password=password) as device_conn:
facts = device_conn.get_facts()
click.echo(facts)
if __name__ == "__main__":
get_device_facts()
Running the Command¶
ntc@jlw:/tmp$ python3 network_cli.py --ip csr1 --platform ios --username ntc
Enter your password:
{'uptime': 39960.0, 'vendor': 'Cisco', 'os_version': 'Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.1.1, RELEASE SOFTWARE (fc3)', 'serial_number': '9AL82RHCRDK', 'model': 'CSR1000V', 'hostname': 'csr1', 'fqdn': 'csr1.ntc.com', 'interface_list': ['GigabitEthernet1', 'GigabitEthernet2', 'GigabitEthernet3', 'GigabitEthernet4']}
ntc@jlw:/tmp$
Creating Basic Command-Line Commands - LAB¶
Add to the script the ability to:
- Add debugging to the script
- Provide the ability to use the
get_interfacesmethod, but default toget_facts
Arguments¶
Click also provides arguments, which are subtly different than options. They require values and will be used similar to positional parameters.
Let's make some minor adjustments to our script:
import click
from napalm import get_network_driver
@click.command()
@click.argument('ip')
@click.argument('platform')
@click.option('--username', prompt='Enter your username', help='Your username')
@click.option('--password', prompt='Enter your password', help='Your password', hide_input=True)
def get_device_facts(platform, ip, username, password):
"""Fetch network device information using Python Napalm."""
driver = get_network_driver(platform)
with driver(hostname=ip, username=username, password=password) as device_conn:
facts = device_conn.get_facts()
click.echo(facts)
if __name__ == "__main__":
get_device_facts()
Arguments have several other features to be aware of:
- File based on arguments, in which you pass the path to the file, which will be used in the next section.
- Environment based variables, in which the variables can be read from an environment variable.
nargsparameter, which controls the order in which data is returned as.
Creating Basic Command-Line Commands - LAB¶
Add to the script the ability to:
- Convert your IP & Platform options into arguments
Organizing Commands with Groups¶
In Click, you can organize related commands using command groups. A command group is created using the @click.group() decorator. This allows you to group related commands under a common command-line entry point. Let's organize commands for interacting with network devices using Napalm into two groups: get and set.
import click
from napalm import get_network_driver
# Create the main command group
@click.group()
def cli():
"""Interact with network devices using Napalm."""
pass
# Add commands to the "get" group
@cli.command()
@click.argument('ip')
@click.argument('platform')
@click.option('--username', prompt='Enter your username', help='Your username')
@click.option('--password', prompt='Enter your password', help='Your password', hide_input=True)
def get(platform, ip, username, password):
"""Get network device facts."""
driver = get_network_driver(platform)
with driver(hostname=ip, username=username, password=password) as device_conn:
facts = device_conn.get_facts()
click.echo(facts)
# Add commands to the "set" group
@cli.command()
@click.argument('ip')
@click.argument('platform')
@click.argument('config_file', type=click.File('r'))
@click.option('--username', prompt='Enter your username', help='Your username')
@click.option('--password', prompt='Enter your password', help='Your password', hide_input=True)
@click.option('--commit', '-c', is_flag=True, help="Commit the configuration.")
def set(platform, ip, config_file, username, password, commit):
"""Apply configuration from a file."""
driver = get_network_driver(platform)
with driver(hostname=ip, username=username, password=password) as device_conn:
config = config_file.read()
device_conn.load_merge_candidate(config=config)
if commit:
device_conn.commit_config()
else:
device_conn.discard_config()
if __name__ == "__main__":
cli()
Warning
In production, we should not overide the builtin set instead we should namespace the function, e.g. set_group and add the decorator @click.group(name='set')
Let's explore what the output looks like now.
ntc@lab:/tmp$ python3 network_cli.py --help
Usage: network_cli.py [OPTIONS] COMMAND [ARGS]...
Interact with network devices using Napalm.
Options:
--help Show this message and exit.
Commands:
get Get network device facts.
set Apply configuration from a file.
ntc@lab:/tmp$ python3 network_cli.py get --help
Usage: network_cli.py get [OPTIONS] IP PLATFORM
Get network device facts.
Options:
--username TEXT Your username
--password TEXT Your password
--help Show this message and exit.
ntc@lab:/tmp$ python3 network_cli.py set --help
Usage: network_cli.py set [OPTIONS] IP PLATFORM CONFIG_FILE
Apply configuration from a file.
Options:
--username TEXT Your username
--password TEXT Your password
--username TEXT Your username
-c, --commit Commit the configuration.
--help Show this message and exit.
To run a group command or a subcommand, use the corresponding command-line syntax. For example:
- To get device facts:
python network_cli.py get - To apply a configuration:
python network_cli set
As you can see, you now have nested cli commands each with their own arguments and options.
Creating Basic Command-Line Commands - LAB¶
Add to the script the ability to:
- Add different groups for getting facts vs getting interfaces
- Create an option on interfaces to only show interfaces that are up
Console Scripts¶
While under the hood, this is actually leveraging setuptools' Python entry point integration, it is helpful to understand this pattern to create global cli commands that can be ran from anywhere.
Let's create our pyproject.toml file next to our network_cli.py file.
[tool.poetry]
name = "network_cli"
version = "1.0.0"
description = "A cli tool to manage your network"
authors = ["NTC <info@networktocode.com>"]
[tool.poetry.dependencies]
python = "^3.8"
napalm = "*"
click = "*"
[tool.poetry.scripts]
network_cli = 'network_cli:cli'
The key syntax to understand is:
- Must be placed in
[tool.poetry.scripts] - The key name is what the command line will be called
- The value is in format of `f"{dotted_file_path}:{function_name}"``
To use this, simply go through your normal poetry commands.
- poetry install
- poetry shell
Now we can be in any directory and have access.
(network-cli-py3.10) ntc@bash:~$ network_cli --help
Usage: network_cli [OPTIONS] COMMAND [ARGS]...
Interact with network devices using Napalm.
Options:
--help Show this message and exit.
Commands:
get Get network device facts.
set Apply configuration from a file.
(network-cli-py3.10) ntc@bash:~$
Console Scripts - LAB¶
- Create the console script as shown in the demo
- Make it accessible even if not in the environment
- Hint: This is a poetry process
Advanced Features & Best Practices¶
- Setting custom exit codes with
click.Abort(). - Installing and using third-party Click plugins.
- Extending an existing click app.
- Organizing commands and options logically and often with different files and folders.