Usando argparse, tox e setuptools para fazer aplicativo de cli em python
Vou escrever um aplicativo “hello world” para aprender como escrever, testar e deployar um aplicativo de linha de comando.
Criando aplicação
O python já vem com uma biblioteca para facilitar a criação de aplicações de linha de comando chamada argparse. Vou começar criando o modulo principal:
└── cli
├── main.py
└── __init__.py
O arquivo main.py
vai conter todo o código:
import argparse
def main(argv=None):
parser = argparse.ArgumentParser(prog="greet")
parser.add_argument("name")
args = parser.parse_args(argv)
print(f"hello {args.name}")
return 0
if __name__ == "__main__":
exit(main())
Vou testar se o módulo está funcionando usando a linha de comando:
$ python -m cli.main rafael
hello rafael
Sucesso! Mas caso a minha aplicação comece a ficar mais complexa não vou querer testar manualmente todas as possibilidades. Vou criar um teste para garantir que essa lógica esteja sempre correta.
Escrevendo testes
Criando o mudulo de testes:
├── cli
│ ├── __init__.py
│ ├── main.py
└── tests
├── __init__.py
└── test_cli.py
Utilizei a própria função main
para passar os argumentos e o pytest também
ajuda a checar o stdout
utilizando o argumento capsys
:
from cli.main import main
def test_hello_world_cli(capsys):
main(["test"])
result = capsys.readouterr()
assert result.out == "hello test\n"
Usando pytest:
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.10.4, pytest-7.1.3, pluggy-1.0.0
[...]
collected 1 item
tests/test_cli.py . [100%]
============================== 1 passed in 0.01s ===============================
Mas ter que rodar o pytest instalado globalmente não é tão bom assim. Prefiro utilizar
a ferramenta tox, que vai automatizar esses testes serem executados em vários ambientes
diferentes. Mas antes disso é preciso empacotar a nossa aplicação utilizando o
setuptoopls
.
Empacotando usando setuptools
O setuptools
precisa de alguns arquivos de configuração para empacotar
o aplicativo, são eles: setup.py
, setup.cfg
e pyproject.toml
. Após criar eles
na raiz do projeto, é preciso preencher o setup.py
e pyproject.toml
com valores
padrão:
from setuptools import setup
setup()
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "wheel"]
Agora vou criar o arquivo principal, setup.cfg
:
[metadata]
name = greet
version = 0.0.1
[options]
packages = cli
[options.entry_points]
console_scripts =
greet = cli.main:main
Agora basta usar o comando build:
$ python -m build
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (setuptools, wheel)
* Getting dependencies for sdist...
[...]
Successfully built greet-0.0.1.tar.gz and greet-0.0.1-py3-none-any.whl
Temos o nosso pacote na pasta dist/
. Para instalar basta usar pip:
$ pip install dist/greet-0.0.1-py3-none-any.whl
Processing ./dist/greet-0.0.1-py3-none-any.whl
Installing collected packages: greet
Successfully installed greet-0.0.1
Agora a aplicação foi instalada, basta usar:
$ greet rafael
hello rafael
Finalmente posso usar o tox para rodar os testes.
Rodando testes com tox
Adicionando o arquivo tox.ini
na raiz do projeto com uma configuração bem básica:
[tox]
envlist = py310
[testenv]
deps = pytest
commands =
pytest {posargs:tests}
Para rodar os testes em todos os ambientes disponíveis na sua máquina:
$ tox --skip-missing-interpreters
Caso eu queira rodar um teste especifico em um ambiente específico:
$ tox -e py310 -- tests/test_cli.py::test_hello_world_cli
Black, mypy e pre-commit
Também vou adicionar algumas ferramentas para ajudar no desenvolvimento. Mais
um arquivo de configuração na raiz do projeto chamado .pre-commit-config.yaml
:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.982
hooks:
- id: mypy
additional_dependencies: [types-all]
exclude: tests
A ideia é usar o pre-commit
para sempre utilizar o mypy
e black
antes de
cada commit. Para iniciar no projeto basta:
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
Caso queira executar sem fazer commit basta usar:
$ pre-commit run --all-files
Conclusão
Esse foi o esqueleto básico de uma aplicação de linha de comando usando python. O código-fonte mostrado nesse post se encontra no meu github.