Introdução
Existem inúmeros pacotes e frameworks para construir aplicações em linha de comando de forma fácil e bonita.
Neste post iremos abordar algumas ferramentas que utilizo e construir uma pequena aplicação de linha de comando de exemplo.
Let’s code
Começando do início: vamos criar uma nova pasta em algum local de sua preferência.
mkdir my_cli
cd my_cli
Agora vamos iniciar um novo virtual environment, para isolar nossa aplicação e os pacotes instalados do restante da instalação padrão do Python. Você pode aprender mais sobre os venvs do Python na documentação oficial do VENV.
python3 -m venv .venv
A seguir, precisamos ativar o venv criado:
source .venv/bin/activate
Para ter certeza de que o seu venv está ativo, verifique se há uma observação entre parênteses no seu shell, como o exemplo abaixo:
O próximo passo é instalar uma biblioteca própria para a criação da nossa aplicação CLI: Typer. Essa biblioteca é extremamente fácil e versátil.
Para instalar, digite o comando abaixo:
pip install "typer[all]"
Agora vamos iniciar nosso CLI. Crie um arquivo chamado main.py:
touch main.py
Abra-o com algum editor de código de sua preferência. No meu caso, utilizo o PyCharm.
Adicione o seguinte conteúdo no arquivo:
import typer
app = typer.Typer()
@app.command()
def primeiro_comando():
print('Olá! Esse é meu primeiro comando.')
@app.command()
def segundo_comando():
print('Olá! Esse é meu segundo comando.')
if __name__ == '__main__':
app()
Resumindo o que estamos fazendo:
import typer
: Importando a biblioteca que instalamos.app = typer.Typer()
: Criando um objeto app do tipo Typer.@app.command()
: Adicionando um comando ao meu objeto app. No nosso caso, adicionamos dois comandos. O comando executa a função onde o decorator está:primeiro_comando
esegundo_comando
Executando nosso arquivo, iremos obter um erro:
Observe que, com poucas linhas, nosso código já entende que há dois comandos disponíveis e que nenhum deles foi informado na execução. Tudo isso graças ao Typer.
Agora execute novamente passando a opção –help como parâmetro:
O Typer nos gera essa orientação automaticamente: Qual a forma correta de utilizar o arquivo, as opções e também os comandos disponíveis.
Notou a interface colorida? O Typer utiliza a biblioteca Rich, que deixa o retorno para o usuário muito mais bonito.
Executando um de nossos comandos:
E temos o retorno que adicionamos na função.
Definindo nossa aplicação
Para este exemplo, iremos construir um CLI que consulta uma API pública e retorna fatos aleatórios sobre gatos ou cachorros (sim, muito útil).
Para consultar API’s externas, utilizaremos o pacote chamado HTTPx.
Para termos uma opção diferente no uso das opções em nosso CLI, vamos também utilizar a biblioteca Inquirer.
Instale os pacotes:
pip install httpx inquirer
Vamos agora criar um arquivo para fazermos as requisições para as API’s de onde virão os fatos sobre os gatos e cachorros. Vou chamar de api.py:
Lembrando que as API’s utilizadas aqui podem não estar funcionando mais…
import httpx
def get_random_dog_fact():
try:
response = httpx.get('https://dog-api.kinduff.com/api/facts')
response.raise_for_status()
fact = response.json()
return fact.get('facts', '')[0]
except Exception as e:
print(e)
def get_random_cat_fact():
try:
response = httpx.get('https://cat-fact.herokuapp.com/facts/random?amount=1')
response.raise_for_status()
fact = response.json()
return fact.get('text', '')
except Exception as e:
print(e)
Lembre-se: aqui utilizamos duas API’s para exemplo, mas essas funções que são executadas podem ser qualquer outra rotina que você faz normalmente. Agora vamos alterar nosso comando para fazer algo útil: buscar os fatos.
Renomeie a de função primeiro_comando para buscar, e chame a função da API para ter certeza de que está funcionando:
import typer
import api
app = typer.Typer()
@app.command()
def buscar():
facts = api.get_random_dog_fact()
print(facts)
@app.command()
def segundo_comando():
print('Olá! Esse é meu segundo comando.')
if __name__ == '__main__':
app()
Ao executar nosso CLI, iremos printar na tela o retorno da API:
Lembre-se: a API retorna fatos aleatórios de gatos e cachorros, portanto a saída deve ter sido com uma saída diferente da imagem acima.
Vamos melhorar nosso comando para permitir que o usuário escolha entre cachorro ou gato, utilizando o Inquirer.
import typer
import inquirer
import api
app = typer.Typer()
@app.command()
def buscar():
question = [
inquirer.List('animal',
message="Sobre qual animal você quer saber um fato?",
choices=['Cachorros', 'Gatos'],
),
]
answers = inquirer.prompt(question)
if answers.get('animal') == 'Cachorros':
facts = api.get_random_dog_fact()
elif answers.get('animal') == 'Gatos':
facts = api.get_random_cat_fact()
else:
facts = "Escolha uma opção válida!"
print(facts)
@app.command()
def segundo_comando():
print('Olá! Esse é meu segundo comando.')
if __name__ == '__main__':
app()
Execute novamente, e veja:
O Inquirer gerou uma lista com as opções que informamos no código, e permitiu que o usuário utilize as setas do teclado para selecionar qual opção é a desejada. Ao selecionar e teclar enter, um determinado trecho de código é executado pelo if/else.
Selecionando as opções, as chamadas da API são feitas:
Agora, vamos estilizar mais ainda com o Rich Progress:
import typer
import inquirer
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn
import api
app = typer.Typer()
console = Console()
@app.command()
def buscar():
question = [
inquirer.List('animal',
message="Sobre qual animal você quer saber um fato?",
choices=['Cachorros', 'Gatos'],
),
]
answers = inquirer.prompt(question)
with Progress(
SpinnerColumn(),
*Progress.get_default_columns(),
TimeElapsedColumn(),
console=console
) as progress:
if answers.get('animal') == 'Cachorros':
task1 = progress.add_task(
description="[cyan]Buscando fatos de cachorros...",
total=None,
)
facts = api.get_random_dog_fact()
progress.update(
task1,
completed=1,
total=1,
refresh=True,
description="[green]Buscando fatos de cachorros... OK"
if facts
else "[red]Buscando fatos de cachorros... FALHA!",
)
elif answers.get('animal') == 'Gatos':
task2 = progress.add_task(
description="[cyan]Buscando fatos de gatos...",
total=None,
)
facts = api.get_random_cat_fact()
progress.update(
task2,
completed=1,
total=1,
refresh=True,
description="[green]Buscando fatos de gatos... OK"
if facts
else "[red]Buscando fatos de gatos... FALHA!",
)
else:
facts = "Escolha uma opção válida!"
print(facts)
@app.command()
def segundo_comando():
print('Olá! Esse é meu segundo comando.')
if __name__ == '__main__':
app()
Ao chamar a API, como temos que aguardar o retorno, é exibido uma barra de progresso animada e bonita:
Assim que a chamada é concluída, o fato é impresso na tela e a barra é alterada para verde, para indicar o sucesso da execução:
Existem inúmeras customizações e configurações do Rich, você pode consultar a documentação oficial para mais detalhes.
Por hora temos um CLI com apenas um comando, que depende da escolha do usuário para executar determinadas ações.
O próximo passo é a compilação do pacote para ser instalado em outros locais, mas ficará para um próximo post.
tchau! =)