$ whoami
Turicas, prazer! =)
Sigam-me os bons:
Software Livre & Python
(desde 2004/2005)
## Agenda
- Definições e contexto histórico
- CLI como Usuário(a) (+ exercícios)
- Características amáveis
- Características detestáveis
- O módulo `argparse` (+ exercícios)
- Resumo: Boas Práticas
- Sugestões pós-tutorial
## Alinhamento de Expectativas
- Esse **não é um tutorial para iniciantes em programação**
- É necessário:
- Saber lógica de programação
- Saber programar em Python
- Saber usar o terminal de sistemas Unix-like (`cd`, `ls`, `grep` etc.)
- Ter Python (3.9+) e um editor de código instalados
- Sistema operacional: Unix-like (GNU/Linux, *BSD, Mac OS X) ou Windows com WSL
- "Terminal" do Windows não será o foco
- Todos(as) são bem-vindos(as), mas o foco para dúvidas será nas pessoas que já sabem os conceitos acima
- Os exercícios exigirão muita atenção, digitação e serão cumulativos. Esteja focado(a)
- Por conta do tempo, não conseguiremos ir muito profundamente em todos os assuntos, mas existem sugestões com links
# Definições e Contexto Histórico
Graphical User Interface (GUI)
Exemplo: Gnome Files
Terminal User Interface (TUI)
Exemplo: htop
Quais outros exemplos de TUI vocês conhecem?
Command-Line Interface (CLI)
Exemplo: cat
## Command-Line Interface (2)
- Comando é invocado com parâmetros, executa, mostra a saída e nos devolve o prompt
- CLI não tem "user"
- Crie o seu programa para que ele seja usado por outros programas!
- "_Human first, machine friendly_"
- CLI não é TUI
- TUI não é só "janela no terminal": existem programas interativos, como o shell do Python
## Unix
### O SO mais influente das últimas décadas
- Criado em 1969 por Ken Thompson e Dennis Ritchie
- A linguagem C foi criada para escrever o Unix!
- Portável par várias máquinas
- Conexão lenta entre terminais e computadores
- Richard Stallman cria o conceito de _free software_ (software livre) em 1983 e começa o GNU (GNU's Not Unix),
o sistema operacional livre compatível com o Unix
- Em 1975 funda a [Free Software Foundation](https://fsf.org/)
- Em 1988 o POSIX é iniciado, com o objetivo de padronizar as diversas implementações Unix-like
## Pedindo ajuda
- Alguém **não conhece** o comando `ls`?
- Quem conhece mais de 5 opções?
- `ls --help`
- `ls -h`
- `--help` e `-h` é convenção
## Manuais ❤️
- `man [seção] comando`
- `help comando_embutido` (no `bash`, `cd` é um comando embutido e não um programa, como o `ls`)
- Busca: `man -k termo`
- Convenções:
- **bold text**: digite exatamente como está
- _italic text_: troque com o valor apropriado
- `[-abc]`: argumentos entre `[` e `]` são opcionais
- `-a|-b`: opções que não podem ser usadas juntas
- `argument ...`: argumento pode ser especificado N vezes
- Exemplo: `man ls` (`q` para sair)
- Exemplo: `man grep` (`/` para buscar)
- `/EXIT` + ENTER
## Códigos de saída
- O programa retorna o código de saída (inteiro) para o sistema operacional, como se fosse uma função
- `0` = deu tudo certo!
- `1-255` = algum erro aconteceu
- Cada número representa algo específico **apenas para aquele programa**!
- Testem o `grep` com algum arquivo
```
# Encontrado
$ grep pythonic index.html
$ echo $?
0
# Não encontrado
$ grep zig index.html
$ echo $?
1
```
Quem não lê manual
## Parâmetros e opções
- Entenda cada comando como um verbo
- Transitivos diretos, indiretos e intransitivos
- Ordem não importa: `ls -lh` = `ls -l -h` = `ls -h -l` = `ls -hl`
- Forma longa: `ls --width=60` = `ls --width 60`
- Forma curta: `ls -w 60` = `ls -w60`
- Ordem importa para as opções que precisam de um valor:
- `ls -w 60` funciona, `ls -lw 60` funciona, mas `ls -wl 60` não
## Exemplos na stdlib
- A biblioteca padrão do Python possui diversos utilitários de linha de comando:
- (busque por outros, como `http.server`)
```
$ echo '{"a":123,"b":"aaa"}' > test.json
$ cat test.json
{"a":123,"b":"aaa"}
$ python -m json.tool test.json
{
"a": 123,
"b": "aaa"
}
$ cat test.json | python -m json.tool
{
"a": 123,
"b": "aaa"
}
$ python -m mimetypes test.json
type: application/json encoding: None
```
# Características amáveis
## Inspirações
- [The Art of Unix Programming](http://www.catb.org/esr/writings/taoup/html/), por Eric S. Raymond
- [Portable Operating System Interface (POSIX)](https://pt.wikipedia.org/wiki/POSIX)
- [Command Line Interface Guidelines (CLIG)](https://clig.dev/)
- [Programação Shell Linux](https://www.amazon.com.br/Programa%C3%A7%C3%A3o-Shell-Linux-Julio-Cezar/dp/8574528331), por Julio Cezar Neves
- Filosofia Unix, pela cultura de desenvolvimento/uso dos programas (é tipo o [Zen do Python](https://pt.wikipedia.org/wiki/Zen_de_Python))
## Rules of Unix (1/3)
- 1- Rule of Modularity: Write simple parts connected by clean interfaces.
- 2- Rule of Clarity: Clarity is better than cleverness.
- **3- Rule of Composition**: Design programs to be connected to other programs.
- **4- Rule of Separation**: Separate policy from mechanism; separate interfaces from engines.
- 5- Rule of Simplicity: Design for simplicity; add complexity only where you must.
- 6- Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do.
## Rules of Unix (2/3)
- **7- Rule of Transparency**: Design for visibility to make inspection and debugging easier.
- **8- Rule of Robustness**: Robustness is the child of transparency and simplicity.
- **9- Rule of Representation**: Fold knowledge into data so program logic can be stupid and robust.
- **10- Rule of Least Surprise**: In interface design, always do the least surprising thing.
- **11- Rule of Silence**: When a program has nothing surprising to say, it should say nothing.
- **12- Rule of Repair**: When you must fail, fail noisily and as soon as possible.
## Rules of Unix (3/3)
- **13- Rule of Economy**: Programmer time is expensive; conserve it in preference to machine time.
- 14- Rule of Generation: Avoid hand-hacking; write programs to write programs when you can.
- **15- Rule of Optimization**: Prototype before polishing. Get it working before you optimize it.
- 16- Rule of Diversity: Distrust all claims for “one true way”.
- 17- Rule of Extensibility: Design for the future, because it will be here sooner than you think.
## Rules of Unix
- "_If you're new to Unix, these principles are worth some meditation_"
- "_Software-engineering texts recommend most of them_"
- "_Most other operating systems lack the right tools and traditions to turn them into practice_"
- Retirado de [The Art of Unix Programming](http://www.catb.org/esr/writings/taoup/html/), por Eric S. Raymond
- Inspirado em Ken Thompson, Doug McIlroy, Rob Pike e na filosofia Unix (Usenet)
“
Those who do not understand Unix are condemned to reinvent it, poorly.
”
-- Henry Spencer (Usenet signature, 1987-11-15)
# Características detestáveis
CLI também é UI
## Cria serviço com o nome `--help`
```
$ dokku postgres:create --help
Waiting for container to be ready
Creating container database
Securing connection to database
=====> Postgres container created: --help
=====> --help postgres service information
Config dir: /var/lib/dokku/services/postgres/--help/data
Config options:
Data dir: /var/lib/dokku/services/postgres/--help/data
Dsn: postgres://postgres:6272ceeb9b57335182ad6aef639df714@dokku-postgres---help:5432/__help
Exposed ports: -
Id: 343581fde373f4bb3c22e68b5d6ab4d4f4d5668b953d4c32275caddc967704aa
Internal ip: 172.17.0.8
Initial network:
Links: -
Post create network:
Post start network:
Service root: /var/lib/dokku/services/postgres/--help
Status: running
Version: postgres:17.5
```
## Ignora parâmetro, sem mostrar erro
```
dokku plugin:install https://github.com/dokku/dokku-postgres.git blablabla
[...]
```
## Parâmetro obrigatório como opção (--)
```
$ tree-sitter complete
error: the following required arguments were not provided:
--shell
Usage: tree-sitter complete --shell
For more information, try '--help'.
(system) turicas@thinkpad:~
$ tree-sitter complete --help
Generate shell completions
Usage: tree-sitter complete --shell
Options:
-s, --shell The shell to generate completions for [possible values: bash, elvish, fish, power-shell, zsh, nushell]
-h, --help Print help
```
## Ordem das opções influencia
## Não segue padrão de opções longas (--)
```
find . -name '*.pdf' # Funciona
find -name '*.pdf' . # NÃO FUNCIONA!
```
## Erro de documentação
Pode ser especificada várias vezes, mas é apresentada como "stringArray":
```
$ docker build --help | grep build-arg
--build-arg stringArray Set build-time variables
```
## Inconsistência entre opções curtas e longas
```
mariadb --help | grep -A 2 '\--password'
-p, --password[=name]
Password to use when connecting to server. If password is
not given it's asked from the tty.
$ mariadb -u $MARIADB_USER -p $MARIADB_PASSWORD
Enter password:
ERROR 1045 (28000): Access denied for user 'mariadb'@'localhost' (using password: YES)
$ mariadb -u $MARIADB_USER -p=$MARIADB_PASSWORD
ERROR 1045 (28000): Access denied for user 'mariadb'@'localhost' (using password: YES)
$ mariadb -u $MARIADB_USER --password $MARIADB_PASSWORD
Enter password: ERROR 1045 (28000): Access denied for user 'mariadb'@'localhost' (using password: NO)
$ mariadb -u $MARIADB_USER --password=$MARIADB_PASSWORD
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 41
Server version: 11.7.2-MariaDB-ubu2404 mariadb.org binary distribution
```
## Inconsistência na posição de parâmetro que não existe
```
# Erro
$ dokku postgres:logs -f pg17_brasil-io-prd
! Postgres service -f does not exist
# Sem erro, mas não funciona como o esperado
$ dokku postgres:logs pg17_brasil-io-prd -f
PostgreSQL Database directory appears to contain a database; Skipping initialization
[...]
(não atualiza)
# Funciona como o esperado (equivalente ao `tail -f`)
$ dokku postgres:logs pg17_brasil-io-prd -t
PostgreSQL Database directory appears to contain a database; Skipping initialization
[...]
(atualiza)
```
## Por que `argparse` e não `X`?
- Dar vida (facilmente) a funções/classes que já você tem
- É uma evolução de soluções anteriores (`getopt` e `optparse`)
- Possui funcionalidades suficientes para uma boa interface
- Não adiciona dependências externas ao projeto
- Pode usar para criar _management commands_ do Django (que ajudam bastante!)
## Por que a insistência em usar stdlib?
- Você é **responsável** pelas dependências que adiciona
- E em geral você não tem controle sobre elas!
- Módulos da stdlib do Python são **MUITO mais testados** do que bibliotecas de terceiros
- Evitar _hypes_ (quem trabalha com tecnologia adora uma, desnecessariamente)
- Evita ataques de _supply chain_ e problemas que você não pode resolver. Busque sobre:
- Heartbleed (2014) - OpenSSL
- Leftpad (2016) - npm
“
Those who do not understand Python stdlib are condemned to reinvent it, poorly (and waste Internet traffic
and disk space).
”
-- Álvaro Justen (Python Brasil, 2025-10-22)
Processo POSIX (2)
## Exercícios
- Exemplos inspirados na CLI da biblioteca [mercados](https://pypi.org/project/mercados)
- Palestra domingo às 9h
- Baseados no arquivo `banco_central.py` (simplificação de `mercados.bcb`)
- Passos de bebê
## `banco_central.py`
- Baixe-o em [`banco_central.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/banco_central.py)
- Entenda as funções e a `dataclass` implementadas no arquivo:
- Baixa séries temporais da API, extrai e guarda em uma lista de dataclasses
- Converte a lista para CSV
- Converte a lista para markdown
- Desenho das funcionalidades e forma de uso:
- Uma das partes mais importantes do desenvolvimento da CLI
- Como você desenharia uma CLI para contemplar essas funcionalidades?
- O que é importante para o(a) usuário(a)?
- Qual seria a melhor forma de chamá-la e passar as opções?
## Usando `sys.argv`
- Arquivo: [`00_sys_argv.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/00_sys_argv.py)
- Quais são os pontos negativos dessa solução?
- Em termos de usabilidade
- Em termos de robustez
- Respostas:
- ⬆️ Divisão em _model_, _view_ e _controller_
- ⬇️ Mensagem de erro não intuitiva quando usuário(a) não passa parâmetros
- ⬇️ Sem `--help`
## Usando `argparse`
- Arquivo: [`01_argparse.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/01_argparse.py)
- `git diff --no-index 00_sys_argv.py 01_argparse.py`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ `argparse` vai PARAR o programa se os argumentos não forem extraídos com sucesso
- ⬆️ `-h` e `--help`
- Usuário(a) agora sabe os nomes do parâmetros
- ⬆️ Código de saída coerente
## Usando `choices`
- Arquivo: [`02_choices.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/02_choices.py)
- `git diff --no-index 01_argparse.py 02_choices.py`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Refatoração: [`02_choices_2.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/02_choices_2.py)
- `git diff --no-index 02_choices.py 02_choices_2.py`
- Respostas:
- ⬆️ Validação de dados (programa sempre executará de forma "segura")
## Usando opções
- Arquivo: [`03_opcoes.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/03_opcoes.py)
- `git diff --no-index 02_choices.py 03_opcoes.py`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Melhoria (`os.environ`): [`03_opcoes_2.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/03_opcoes_2.py)
- `FORMATO_BC=csv python 03_opcoes_2.py IPCA`
- `git diff --no-index 03_opcoes.py 03_opcoes_2.py`
- Respostas:
- ⬆️ Exige menos parâmetros
- ⬆️ _Sane defaults_
- ⬆️ Integra-se ao restante do sistema (env vars)
## Opções: use _kebab-case_
- Para opções longas de múltiplas palavras, separe as palavras com hífens (e não com _underscore_)
- O `argparse` criará os argumentos com os hífens trocados por _underscores_:
```
parser = argparse.ArgumentParser()
parser.add_argument("--codigo-negociacao")
args = parser.parse_args()
print(args.codigo_negociacao)
```
## Usando `type`
- Arquivo: [`04_type.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/04_type.py)
- `git diff --no-index 03_opcoes.py 04_type.py`
- Execute:
- `python 04_type.py -m 5 IPCA`
- `python 04_type.py IPCA -m 5`
- `python 04_type.py IPCA -m teste`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Flexibilidade no controle dos resultados
- ⬇️ Não possui controle pela data (início/fim)
## Usando `help`, `description`, `metavar` e `version`
- Arquivo: [`05_help.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/05_help.py)
- `git diff --no-index 04_type.py 05_help.py`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Mais clareza na documentação/`--help`
- ⬇️ Não possui controle pela data (início/fim)
## `stdout` vs `stderr`
- Toda saída que pode ir para outros programas (ou arquivos) vai no `stdout`
- Mensagens de erro e/ou status vão no `stderr`
- Arquivo: [`06_stderr.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/06_stderr.py)
- `git diff --no-index 05_help.py 06_stderr.py`
- Qual a diferença entre:
- `python 06_stderr.py IPCA`
- e `python 06_stderr.py IPCA > ipca.md`?
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Mensagem no `stderr` dá mais transparência
## `action="store_true"`
- Guarda _flags_ (opções que não recebem argumentos)
- Arquivo: [`07_store_true.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/07_store_true.py)
- `git diff --no-index 06_stderr.py 07_store_true.py`
- Qual a diferença entre:
- `python 07_store_true.py IPCA`
- e `python 07_store_true.py --quiet IPCA`
- (ou `python 07_store_true.py -q IPCA`)?
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Flexibilidade em poder não ver a mensagem de status
## Opções conflitantes (1/2)
- Arquivo: [`08_mutual_exclusion.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/08_mutual_exclusion.py)
- `git diff --no-index 07_store_true.py 08_mutual_exclusion.py`
- Tente executar:
- `python 08_mutual_exclusion.py --verbose IPCA -m 10`
- `python 08_mutual_exclusion.py --quiet IPCA -m 10`
- `python 08_mutual_exclusion.py --verbose --quiet IPCA -m 10`
## Opções conflitantes (2/2)
- Implementação de grupo exclusivo: [`08_mutual_exclusion_2.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/08_mutual_exclusion_2.py)
- `git diff --no-index 08_mutual_exclusion.py 08_mutual_exclusion_2.py`
- Tente executar:
- `python 08_mutual_exclusion_2.py --quiet --verbose -m 10 IPCA`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Validação de regra de negócio não permite que o usuário entre com informações conflitantes
## Usando tipo personalizável
- Arquivo: [`09_custom_type.py`](https://github.com/turicas/slides/blob/gh-pages/tutorial-cli/src/09_custom_type.py)
- `git diff --no-index 08_mutual_exclusion_2.py 09_custom_type.py`
- Tente executar:
- `python 09_custom_type IPCA -i xxxx`
- Logo depois: `echo $?`
- O que melhoramos com relação à anterior? Quais são os pontos negativos dessa solução?
- Respostas:
- ⬆️ Mais flexibilidade no controle dos resultados
## Sugestões de Exercícios (1/2)
- Implemente um parâmetro posicional `arquivo` com `required=False`
- Se especificado, salva o resultado (que iria pra stdout) no arquivo
- Use `Path(arquivo).parent.mkdir(exist_ok=True, parents=True)`
- Verifique (`try`/`except`) se houve erro de _timeout_ na função `series_temporais` - em caso positivo,
termine imediatamente o programa com um código de erro (ex: `exit(10)`)
- Transforme a definição do `parser` em uma função (da linha `parser = ...` à linha anterior a `args = parser.parse_args()`) e escreva testes automatizados para ele
## Sugestões de Exercícios (2/2)
- Mova o código da CLI para o arquivo `banco_central.py` e coloque-o dentro de `if __name__ == "__main__":`,
assim você terá uma biblioteca e CLI em um único módulo
- Altere o programa para aceitar atalhos para as mesmas séries, como "ipca", "ipca15", "ipca-15" e "selic"
- Como você implementaria cache (para evitar múltiplas requisições)?
- Dica: existem pastas padrão para cache em cada sistema operacional
## Conteúdo Extra
- Além do `action="store_true"` e _mutual exclusion_, busque [na
documentação](https://docs.python.org/3/library/argparse.html) sobre:
- `action="append"`
- `action="count"`
- `nargs="?"`
- `nargs="*"`
- `nargs="+"` (diferente de `action="append"`)
- `parser.add_subparsers()` (para subcomandos, como o `git`)
## Boas Práticas (1)
- Não mostre mensagens em caso de sucesso (exceto se `--verbose` ou `--debug` forem especificadas)
- Se encontrar algum problema, exiba um erro imediatamente
- Retorne `0` se o programa executou com sucesso e `1-255` se falhou
- Seja consistente com os códigos de saída
- Crie diretórios pais antes de salvar arquivos:
- `Path(arquivo).parent.mkdir(exist_ok=True, parents=True)`
Evite
## Boas Práticas (2)
- Separe _model_, _view_ e _controller_:
- _Model_: dataclasses representando seus dados
- _View_: interface com o usuário (argparse, parâmetros, stdin/stdout/stderr etc.)
- _Controller_: funções/classes/módulos que executam a tarefa de fato
- **ATENÇÃO**: evite `print` ou `input` no _controller_ (em geral isso é de responsabilidade da _view_)
- Teste cada uma dessas camadas de maneira independente
## Boas Práticas (3)
### Importe somente o necessário
- Esqueça a PEP8 por um instante
- Exemplo: rows 0.4.1 (~700ms) vs 0.5.0+dev0 (~100ms)
- `time ls --help` executa em 3ms! (é feito em C)
- `touch empty.py; time python empty.py` executa em ~20ms (sem nenhum código Python)
- O `--help` deve estar abaixo de 100ms (acima disso a lentidão é perceptível e não justificável para uma
mensagem de ajuda)
## Outras Bibliotecas
### (ordem alfabética - não é recomendação!)
- [argparse-manpage](https://pypi.org/project/argparse-manpage/)
- [blessed](https://blessed.readthedocs.io/en/latest/terminal.html)
- [click](https://pypi.org/project/click/)
- [cmd (stdlib)](https://docs.python.org/3/library/cmd.html)
- [curses (stdlib)](https://docs.python.org/3/howto/curses.html) (não funciona em Windows)
- [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) (ex: pytmux, pyvim)
- [PyTermGUI](https://ptg.bczsalba.com/)
- [rich](https://github.com/Textualize/rich)
- [textual](https://github.com/Textualize/textual)
- [tqdm](http://pypi.python.org/project/tqdm)
- [typer](https://pypi.org/project/typer/)
- [urwid](https://urwid.org/index.html)
## Conteúdos complementares
- [The Art of Unix Programming](http://www.catb.org/esr/writings/taoup/html/), por Eric S. Raymond (livro,
Inglês)
- [Portable Operating System Interface (POSIX)](https://pt.wikipedia.org/wiki/POSIX)
- [Command Line Interface Guidelines (CLIG)](https://clig.dev/) (artigo, Inglês)
- [Programação Shell
Linux](https://www.amazon.com.br/Programa%C3%A7%C3%A3o-Shell-Linux-Julio-Cezar/dp/8574528331), por Julio Cezar
Neves (livro, Português)
- [Terminal under the hood](https://yakout.io/blog/terminal-under-the-hood/) (artigo, Inglês)
- [Mastering Linux Man Pages](https://youtu.be/RzAkjX_9B7E?si=HlkR29qRqOybRcdy) (vídeo, Inglês)
## Slides: [bit.ly/turicas-cli-pybr25](http://bit.ly/turicas-cli-pybr25)
## Exemplos de código: [bit.ly/turicas-cli-pybr25-src](http://bit.ly/turicas-cli-pybr25-src)