Основы click#
В click описание интерфейса командной строки (CLI) построено на декораторах:
аргументы создаются с помощью декоратора click.argument
опции с помощью декоратора click.option
Пример скрипта, который пингует только один IP-адрес (ping_ip.py):
import subprocess
def ping_ip(ip_address, count):
"""
Ping IP address and return True/False
"""
reply = subprocess.run(
f"ping -c {count} -n {ip_address}",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if reply.returncode == 0:
return True
else:
return False
if __name__ == "__main__":
ip = "8.8.8.8"
if ping_ip(ip, count=3):
print(f"IP-адрес {ip:15} пингуется")
else:
print(f"IP-адрес {ip:15} не пингуется")
Первое, что нужно сделать, чтобы добавить CLI к этому скрипту - перенести
код из блока if __name__ == "__main__"
в функцию, так как click
применяет декораторы к функции:
def main():
ip = "8.8.8.8"
if ping_ip(ip, count=3):
print(f"IP-адрес {ip:15} пингуется")
else:
print(f"IP-адрес {ip:15} не пингуется")
if __name__ == "__main__":
main()
Следующий шаг - превратить функцию main в команду click. Для этого надо применить декоратор click.command и импортировать click:
import click
@click.command()
def main():
ip = "8.8.8.8"
if ping_ip(ip, count=3):
print(f"IP-адрес {ip:15} пингуется")
else:
print(f"IP-адрес {ip:15} не пингуется")
if __name__ == "__main__":
main()
Теперь вызов скрипта отработает так же, но у скрипта появилась опция –help:
$ python ping_ip_click.py --help
Usage: ping_ip_click.py [OPTIONS]
Options:
--help Show this message and exit.
Так как для выполнения скрипта надо указать IP-адрес, надо добавить соответствующий параметр в CLI. Без IP-адреса скрипт запускать нет смысла, поэтому IP-адрес будут указываться с помощью обязательного параметра - аргумента. Он также указывается декоратором:
@click.command()
@click.argument("ip_address")
def main(ip_address):
if ping_ip(ip_address, count=3):
print(f"IP-адрес {ip_address:15} пингуется")
else:
print(f"IP-адрес {ip_address:15} не пингуется")
if __name__ == "__main__":
main()
Строка @click.argument("ip_address")
указывает, что теперь скрипт ожидает один
обязательный параметр - ip_address, а также функция main должна принимать аргумент с таким именем,
так как click автоматически передаст значение, которое передается при вызове скрипта,
как ключевой аргумент функции, используя имя аргумента.
Теперь опция –help отображает такой вывод:
$ python ping_ip_click.py --help
Usage: ping_ip_click.py [OPTIONS] IP_ADDRESS
Options:
--help Show this message and exit.
И при вызове скрипта обязательно надо передать IP-адрес:
$ python ping_ip_click.py
Usage: ping_ip_click.py [OPTIONS] IP_ADDRESS
Try "ping_ip_click.py --help" for help.
Error: Missing argument "IP_ADDRESS".
$ python ping_ip_click.py 8.8.8.8
IP-адрес 8.8.8.8 пингуется
Так как функция зависит от еще одного значения - count, надо добавить еще один параметр click, в этот раз - опцию. Опции создаются с помощью декоратора click.option:
@click.command()
@click.argument("ip_address")
@click.option("--count", "-c", default=3)
def main(ip_address, count):
if ping_ip(ip_address, count):
print(f"IP-адрес {ip_address:15} пингуется")
else:
print(f"IP-адрес {ip_address:15} не пингуется")
if __name__ == "__main__":
main()
Так же как с аргументом, click будет передавать как ключевой аргумент имя опции и значение, которое было указано при вызове скрипта. Так как в данном случае у опции есть значение по умолчанию, если опция не указана передается значение 3. Еще одно следствие задания значения по умолчанию - click теперь считает, что count обязательно должен быть числом. Это поведение можно менять, указав тип параметра явно, но в данном случае, он подходит.
Запуск скрипта с вводом данных неправильного типа:
$ python ping_ip_click.py 8.8.8.8
IP-адрес 8.8.8.8 пингуется
$ python ping_ip_click.py 8.8.8.8 -c a
Usage: ping_ip_click.py [OPTIONS] IP_ADDRESS
Try "ping_ip_click.py --help" for help.
Error: Invalid value for "--count" / "-c": a is not a valid integer
$ python ping_ip_click.py 8.8.8.8 -c 1
IP-адрес 8.8.8.8 пингуется
И help для текущей версии скрипта:
$ python ping_ip_click.py --help
Usage: ping_ip_click.py [OPTIONS] IP_ADDRESS
Options:
-c, --count INTEGER
--help Show this message and exit.
По умолчанию click не отображает значение, которое указано в default.
Если необходимо это изменить, надо добавить в настройку опции show_default=True
:
$ python ping_ip_click.py --help
Usage: ping_ip_click.py [OPTIONS] IP_ADDRESS
Options:
-c, --count INTEGER [default: 3]
--help Show this message and exit.
Более практичный пример#
Предыдущий пример использовался для демонстрации базовых настроек click и на практике не очень полезен. Чтобы сделать скрипт более интересным, можно добавить возможность отправлять ICMP-запросы на несколько IP-адресов и выводить на стандартный поток вывода информацию о том какие адреса отвечают, а какие нет.
Пример скрипта без использования click:
import subprocess
def ping_ip(ip_address, count):
"""
Ping IP address and return True/False
"""
reply = subprocess.run(
f"ping -c {count} -n {ip_address}",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if reply.returncode == 0:
return True
else:
return False
if __name__ == "__main__":
ip_list = ["8.8.8.8", "8.8.4.4", "10.1.1.1", "192.168.100.1"]
for ip in ip_list:
if ping_ip(ip, count=3):
print(f"IP-адрес {ip:15} пингуется")
else:
print(f"IP-адрес {ip:15} не пингуется")
Пример выполнения скрипта
$ python ping_ip_list.py
IP-адрес 8.8.8.8 пингуется
IP-адрес 8.8.4.4 пингуется
IP-адрес 10.1.1.1 не пингуется
IP-адрес 192.168.100.1 пингуется
Этот скрипт отличается от предыдущего тем, что теперь аргументу передается
не один IP-адрес, а несколько. Click поддерживает такую возможность с помощью
указания nargs в настройках аргумента.
Так как в данном случае количество IP-адресов точно не известно, надо сделать так
чтобы аргумент мог принимать любое количество. Для этого надо указать nargs=-1
и, так как надо передать хотя бы один адрес, дополнительно указать required=True
:
@click.command()
@click.argument("ip_address", nargs=-1, required=True)
@click.option("--count", "-c", default=3)
def main(ip_address, count):
for ip in ip_address:
if ping_ip(ip, count=3):
print(f"IP-адрес {ip:15} пингуется")
else:
print(f"IP-адрес {ip:15} не пингуется")
if __name__ == "__main__":
main()
Опция –help выглядит так:
$ python ping_ip_list_click.py --help
Usage: ping_ip_list_click.py [OPTIONS] IP_ADDRESS...
Options:
-c, --count INTEGER
--help Show this message and exit.
И вызывать скрипт теперь можно таким образом:
$ python ping_ip_list_click.py 8.8.8.8 10.1.1.1 8.8.4.4 192.168.100.1
IP-адрес 8.8.8.8 пингуется
IP-адрес 10.1.1.1 не пингуется
IP-адрес 8.8.4.4 пингуется
IP-адрес 192.168.100.1 пингуется
$ python ping_ip_list_click.py 8.8.8.8 10.1.1.1 8.8.4.4 192.168.100.1 -c 2
IP-адрес 8.8.8.8 пингуется
IP-адрес 10.1.1.1 не пингуется
IP-адрес 8.8.4.4 пингуется
IP-адрес 192.168.100.1 пингуется
Перечисленные IP-адреса попадают в функцию в виде кортежа со строками.