Arquitetura e backend15 min

A estrutura de um projeto (2): passa na minha máquina, falha instalado

Por · fundador e CEO da Hábil ·

Os testes passam e o pacote quebra para quem o instala. O guia da PyPA pede src/, mas a instalação editável também lê a pasta do projeto. Com template.

A estrutura de um projeto · entrega 2 de 7 · Python

Cenário: os personagens e a história são inventados. Os dados técnicos e as citações levam a fonte no final.

Em 1 minuto

  • Ao rodar os testes a partir da raiz do projeto, o Python pode encontrar a pasta antes do pacote instalado. Um arquivo que nunca entrou no pacote passa nos testes e quebra para quem o instala [1][3].
  • O guia de empacotamento do Python (PyPA) resolve isso com uma pasta: src/. Com ela, o código só roda se for instalado [1].
  • Mas atenção: a instalação editável (pip install -e .) também lê a pasta. Com o hatchling, src/ inteira (medido) [5][6].
  • Por isso o template desta entrega testa duas vezes: rápido contra a pasta, e de verdade contra o que foi instalado. E traz um script que demonstra isso ficando vermelho quando deve.
  • Para segunda-feira: rode os testes contra o pacote instalado num ambiente novo. Se algo quebrar, já se sabe o que é.

Neste artigo

  1. O que aconteceu na sexta às 9:30?
  2. O que o guia de empacotamento pede?
  3. Quais são os cinco erros mais comuns?
  4. Exercício: dá para repetir o erro do Iván?
  5. Para levar
  6. O que vem depois?
  7. O template e o curso
  8. O que a Hábil faz?
  9. Referências

O que aconteceu na sexta às 9:30?

Os testes do Iván passavam todos. O pacote que a Sofía instalou não tinha o arquivo que esses testes usavam.

Quinta-feira, 18:10. Terceiro andar.

Depois do Go, a segunda tarefa pareceu fácil: «Fazer o verificador em Python exportar o relatório para CSV». O verificador era o mesmo programa da semana anterior, mas em Python: lia uma lista de serviços, consultava cada um e dizia quais respondiam.

O repositório, herdado outra vez, tinha esta cara:

revisor/          el paquete
tests/
pyproject.toml

Iván escreveu a função num arquivo novo, exportar.py. Deixou-o na raiz, junto de tudo, porque era ali que o via. Em revisor/cli.py acrescentou uma linha: import exportar. Rodou os testes como tinham ensinado, a partir da raiz:

$ python -m pytest -q
.                                                                        [100%]
1 passed in 0.00s

Verde. Subiu a mudança e foi para casa.

Sexta-feira, 9:30.

Sofía, da operação, instalou a versão nova no ambiente dela com pip install . e rodou o comando. O terminal respondeu isto (os caminhos, cortados):

Traceback (most recent call last):
  File ".../bin/revisor", line 3, in <module>
    from revisor.cli import main
  File ".../site-packages/revisor/cli.py", line 1, in <module>
    import exportar
ModuleNotFoundError: No module named 'exportar'

Iván abriu a pasta: exportar.py estava lá. Rodou os testes de novo. Verde outra vez.

— Na minha máquina passa — escreveu no canal.

Lucía, a líder técnica, respondeu com uma pergunta:

— Os testes testaram o que a Sofía instalou, ou a pasta?

Iván ficou olhando para a tela. Nunca tinha lhe ocorrido que fossem duas coisas diferentes.

— A partir da raiz, o Python vê tudo o que está na raiz — continuou Lucía. — A Sofía não tem essa raiz. Tem o pacote. E no pacote o exportar.py nunca entrou.

O que o guia de empacotamento pede?

Que o código importável viva em src/, para que só rode depois de instalado.

O guia oficial de empacotamento do Python, da PyPA, compara os dois layouts em src layout vs flat layout [1]. No layout plano, o pacote vive na raiz, junto do pyproject.toml. No layout src/, desce um nível.

A diferença que importa está na primeira linha da lista: com src/ é preciso instalar o projeto para rodar o código, e com o layout plano não [1]. Parece uma desvantagem. É justamente o ponto.

O guia explica por quê: o Python põe a pasta atual no começo do caminho de busca, então, se nela houver um pacote com o mesmo nome do instalado, ganha o da pasta. E isso, diz o guia, pode deixar a configuração do empacotamento errada sem que ninguém perceba, «which could result in files not being included in a distribution» [1]. Foi o que aconteceu com o Iván.

A pasta atual tem um detalhe: nem sempre entra. python -m pytest a acrescenta; pytest sozinho, não [3][8]. Iván usou a primeira forma, e com ela a raiz inteira ficou à vista.

O template desta entrega, estructura-python, usa o layout src/:

estructura-python/
├── pyproject.toml           el proyecto en un solo archivo
├── servicios.toml           el ejemplo
├── src/
│   └── revisor/             EL PAQUETE: lo único que viaja al instalar
│       ├── __init__.py · __main__.py
│       ├── cli.py           opciones y código de salida
│       ├── servicio.py      el vocabulario. Sin red ni archivos
│       ├── config.py        lee servicios.toml
│       ├── revisar.py       consulta muchos servicios a la vez
│       ├── reporte.py       tabla y JSON
│       └── py.typed
├── tests/                   las pruebas: no viajan
│   ├── conftest.py
│   └── test_*.py
├── herramientas/
│   ├── _comun.sh
│   ├── probar-instalado.sh
│   └── probar-frontera.sh
├── .github/workflows/verificar.yml
└── README.md · LICENSE · LICENSE-CONTENIDO.md

O que decide o que vai junto?

Uma linha do pyproject.toml.

[tool.hatch.build.targets.wheel]
packages = ["src/revisor"]

O que se instala é uma wheel, um arquivo .whl montado a partir do projeto. Essa linha diz ao hatchling, a ferramenta que a constrói, que a wheel leva src/revisor e nada mais [6]. Sem ela, o hatchling adivinha: procura primeiro revisor/__init__.py e depois src/revisor/__init__.py [7]. O template a escreve para que se possa ler.

Assim, tudo o que vive dentro de src/revisor/ vai junto. O que vive fora, não.

Então src/ já protege?

Da raiz, sim. Da instalação editável, não totalmente.

Para trabalhar, o cômodo é instalar em modo editável: pip install -e .. Assim nada é copiado; o Python lê os arquivos onde estão e cada mudança no código aparece na execução seguinte [5]. O pip avisa que a instalação editável e a normal podem se comportar de forma diferente, conforme a ferramenta de build, e que quem usa o pacote «will see the behaviour of regular installs» [5].

E qual é a do hatchling? A documentação dele diz que, numa instalação editável, ele decide quais pastas acrescentar ao caminho de busca conforme os arquivos selecionados [6]. Medimos: acrescenta src/ inteira. Se alguém deixar um src/suelto.py fora do pacote, a instalação editável o encontra e a wheel não o leva.

A mesma página oferece uma saída, a opção dev-mode-exact [6]. Medido: com ela, import suelto dá ModuleNotFoundError. Mas tem custo, e a página avisa: «The dev-mode-exact mechanism is not supported by static analysis tools & IDEs» [6], então o editor deixa de autocompletar. E também não enxerga o que a wheel exclui dentro do pacote (medido). Por isso o template prefere testar a wheel, que é o que recebe quem instala.

O guia da PyPA já antecipa isso: o típico é usar a instalação editável para desenvolver «and a regular installation is used for testing» [1]. O template faz exatamente isso:

pytest                                  # rápido: lee tu carpeta src/
bash herramientas/probar-instalado.sh   # lo que ve quien instala tu paquete

O segundo comando cria um ambiente novo e vazio, instala o projeto sem -e (ou seja, constrói a wheel e a instala) e roda os mesmos testes. No fim, conta os resultados lendo o relatório do pytest, não o código de saída. Estas são as três últimas linhas:

revisor bajo prueba: …/site-packages/revisor (el paquete INSTALADO)
pruebas: 62 · pasaron 62 · fallaron 0 · con error 0 · saltadas 0
✅ lo instalado pasa sus pruebas

Por que os testes vivem fora do pacote?

Para que não vão junto, e para que testem o pacote como qualquer um o veria.

tests/ fica ao lado de src/, não dentro. O pytest roda no modo importlib, o que a documentação dele recomenda para projetos novos [2]: nesse modo, ele não põe pastas do projeto no caminho de busca para importar os testes [4]. O que se testa é o que estiver instalado no ambiente.

E como saber o que está instalado? tests/conftest.py diz isso no cabeçalho de cada execução (o que pytest -q omite):

revisor bajo prueba: src/revisor (la CARPETA del proyecto)

Com a variável REVISOR_EXIGIR_INSTALADO=1, além disso, ele se recusa a rodar se o revisor vier da pasta do projeto. O script do pacote instalado a ativa, e assim não tem como testar o src/revisor local achando que testou a wheel. Atenção: o guarda só olha de onde vem o revisor. Se outro módulo da pasta escapar para os testes, quem o barra é a fronteira, que vem logo a seguir.

O que há no pyproject.toml?

O projeto inteiro, num único arquivo.

O guia da PyPA descreve três tabelas: [build-system], que diz com o que se constrói; [project], com os dados do projeto; e [tool], para cada ferramenta [13]. O template usa as três, mais uma quarta: [dependency-groups], onde vivem pytest, ruff e mypy com a versão fixada. Esse grupo existe desde a PEP 735, que o define exatamente assim: requisitos guardados no pyproject.toml e que não são incluídos em nenhuma distribuição do projeto [16]. Quem instala o revisor não recebe o pytest.

Um detalhe medido: pip install --group dev precisa do pip 25.1 ou mais novo [17], e um ambiente recém-criado no Ubuntu 24.04, a base do Linux Mint 22 [10], vem com o pip 24.0 [11]. A resposta é no such option: --group. Por isso o README pede python -m pip install --upgrade pip antes de tudo.

E onde vai o CSV do Iván?

No pacote, junto dos outros formatos do relatório.

No template, o relatório já tem casa: reporte.py, que hoje converte resultados em tabela ou em JSON. Uma função a_csv vai ali, junto de a_json, e cli.py acrescenta csv às opções de --formato. Nenhum arquivo na raiz. Nenhuma pasta nova.

Na segunda-feira, Iván moveu a função para dentro do pacote e rodou os testes contra o que estava instalado. Verde. Desta vez, verde de verdade.

Como saber que a fronteira existe?

Porque um script a quebra de propósito e exige que o pacote instalado não veja o que ficou de fora.

herramientas/probar-frontera.sh trabalha numa cópia temporária do projeto. Planta src/suelto.py, um módulo que vive em src/ mas fora do pacote, e um teste que o importa. Depois roda a suíte completa duas vezes, em dois ambientes novos com o mesmo Python, a mesma cópia e a mesma versão do pytest. Entre as duas, muda só uma opção do pip, -e:

  1. Controle, com pip install -e. O Python lê a pasta, então todos os testes têm que passar, o plantado incluído. Se não passarem, o experimento está mal montado e o script para no vermelho.
  2. Violação, com pip install. Instala-se a wheel. Tem que falhar um único teste, o plantado, e com a mensagem exata.

Esta é a saída real:

1/2 control: pip install -e (la carpeta) · src/suelto.py está fuera del paquete
   revisor bajo prueba: src/revisor (la CARPETA del proyecto)
   ✅ 63 de 63 pasaron: desde la carpeta, suelto.py se ve
2/2 violación: pip install (la rueda) · el mismo archivo, la misma prueba
   revisor bajo prueba: …/site-packages/revisor (el paquete INSTALADO)
     ✗ test_suelto_viaja_con_el_paquete: ModuleNotFoundError: No module named 'suelto'
   ✅ 62 de 63 pasaron; falló solo la que usa suelto.py, por la razón exacta
✅ frontera: se sostiene

Por que o controle? Porque um experimento que só espera um vermelho também fica «verde» quando falha pelo motivo errado: um teste mal escrito, um ambiente quebrado. Comprovamos: com o teste plantado quebrado de propósito, ou com o controle instalado sem -e, o script para em «o experimento está mal montado».

E se alguém mudar a linha de packages para que a wheel leve todo o src/, a violação passa e o script responde ❌ PASÓ: lo instalado vio src/suelto.py, que no está en el paquete. La frontera no existe. Também comprovamos isso.

O CI do GitHub está escrito para rodar tudo com Python 3.12 e 3.14, a cada mudança, manualmente e toda segunda-feira: ruff, mypy estrito, os testes contra o pacote instalado e a fronteira. Se o repositório público passar 60 dias sem atividade, o GitHub desliga a execução das segundas e é preciso religá-la [23].

Quais são os cinco erros mais comuns?

Os três primeiros vêm de não saber que pasta o Python enxerga; os dois últimos, de descrever o projeto pela metade.

1. Testar a pasta e achar que testou o pacote

O layout plano deixa a raiz à vista, e a raiz não é o que se instala.

É o erro do Iván. O guia da PyPA explica que src/ ajuda a evitar o uso acidental da cópia em desenvolvimento [1]. O pytest pede isso de frente: «it is strongly suggested to use a src layout», sobretudo com o modo padrão [2]. E a documentação do uv diz que uv init prefere pôr o código em src/<nombre>/ [18]; comprovamos com o uv 0.12.23.

A saída não é só trocar de pasta: é testar também o que foi instalado, como faz o template.

2. pip install no Python do sistema

O sistema operacional já administra esse Python, e desde a PEP 668 ele avisa.

A PEP 668 propõe que, por padrão, ferramentas como o pip não instalem nem removam pacotes num Python que o sistema marca como administrado por ele, e que orientem quem tentar para um ambiente virtual [9]. No Ubuntu 24.04, sem ambiente, esta é a resposta (medido):

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

A PEP pede que a opção para forçar «should carry some connotation that its use is risky» [9]. Por isso ela se chama --break-system-packages. Tudo vai dentro de um .venv, como no template.

3. Um arquivo com o nome de um módulo padrão

Um random.py solto esconde o random do Python.

O tutorial oficial avisa: a pasta do script que se executa vai para o começo do caminho de busca, então os arquivos dela são carregados no lugar dos módulos da biblioteca com o mesmo nome, e isso «is an error unless the replacement is intended» [19]. O Python 3.13 acrescentou uma mensagem que aponta isso [12]. Medido com um random.py que importa random:

Python 3.12:
AttributeError: partially initialized module 'random' has no attribute 'randint' (most likely due to a circular import)

Python 3.14:
AttributeError: module 'random' has no attribute 'randint' (consider renaming '…/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)

No 3.12, a mensagem fala de uma importação circular e não do arquivo; o 3.12.3 do Ubuntu 24.04, o do Mint, diz o mesmo (medido). Dentro de um pacote, rodando como pacote (python -m revisor.cli ou o comando instalado), o choque não acontece: revisor/random.py se chama revisor.random. Mas, ao rodar o arquivo como script, python src/revisor/cli.py, ele volta (medido no 3.12 e no 3.14).

4. Dois arquivos de teste com o mesmo nome

No modo padrão do pytest, tests/config/test_formato.py e tests/reporte/test_formato.py se chocam.

A documentação do pytest explica: se os testes não estiverem organizados como pacotes, cada arquivo precisa de um nome único, senão o pytest acusa um erro [4]. Medido com o pytest 9.1.1:

import file mismatch:
imported module 'test_formato' has this __file__ attribute:
  …/tests/config/test_formato.py
which is not the same as the test file we want to collect:
  …/tests/reporte/test_formato.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules

O conserto de sempre era acrescentar __init__.py a tests/, mas isso faz o pytest pôr a raiz do repositório no caminho de busca, e num layout plano o pacote volta a ser importado a partir da pasta [2]. O modo importlib não tem esse problema [2]: com ele, os dois arquivos passam (medido).

5. setup.py ou requirements.txt como única descrição do projeto

O pip não lê um requirements.txt ao instalar o pacote.

Para projetos novos, o guia da PyPA pede a tabela [project] do pyproject.toml, e manter o setup.py só se for preciso configurar algo com código, como compilar extensões em C [13]. E um requirements.txt não é analisado na instalação: só é usado quando alguém roda pip install -r [14]. Se as dependências vivem só ali, quem instala o pacote não as recebe.

Quer que cada máquina instale exatamente as mesmas versões? Para isso agora existe um formato padrão de arquivo de bloqueio, pylock.toml, da PEP 751 [15]. É outro arquivo, para outro problema.

5. setup.py ou requirements.txt como única descrição do projeto
ErroO que o denunciaFonte
1. Testar a pastaVerde na máquina local, ModuleNotFoundError instalado[1][2]
2. pip no Python do sistemaexternally-managed-environment[9]
3. Um random.py soltohas no attribute num módulo que tem, sim[12][19]
4. Testes com o mesmo nomeimport file mismatch[2][4]
5. requirements.txt como única descriçãoInstalado, faltam dependências[13][14]

Exercício: dá para repetir o erro do Iván?

A ideia é pôr um arquivo fora do pacote, ver a pasta dizer que está tudo bem, e ver o pacote instalado dizer que não.

É preciso ter Python 3.12 ou mais novo (python3 --version), Git e bash, uns 20 minutos. O Linux Mint 22 traz o 3.12; se python3 -m venv reclamar que falta o ensurepip, rode sudo apt install python3.12-venv e repita o comando (a lição 1 do curso explica). No macOS, o python3 do sistema é o da Apple, «usually older» [24] (aqui, 3.9.6): instale um recente a partir do python.org. No Windows, use o WSL.

Passo 1. Clone, prepare o ambiente e confira que está tudo verde.

git clone https://github.com/HabilMX/estructura-python
cd estructura-python
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install --group dev -e .
pytest -q
bash herramientas/probar-instalado.sh

O que deve aparecer: 62 passed e, no fim, ✅ lo instalado pasa sus pruebas.

Passo 2. Preveja antes de mexer. A ideia é criar src/exportar.py, fora do pacote, e um teste que o importa. Anote: o que o pytest vai dizer? E o script do pacote instalado?

Passo 3. Repita o erro.

cat > src/exportar.py <<'EOF'
def a_csv(filas: list[tuple[str, str]]) -> str:
    return "\n".join(",".join(fila) for fila in filas)
EOF
cat > tests/test_exportar.py <<'EOF'
from exportar import a_csv


def test_a_csv() -> None:
    assert a_csv([("pagos", "ARRIBA")]) == "pagos,ARRIBA"
EOF
pytest -q
bash herramientas/probar-instalado.sh

O pytest diz 63 passed: a pasta garante que está tudo bem. O script diz outra coisa (o que importa; abaixo vêm a saída do pytest e um ❌):

pruebas: 63 · pasaron 62 · fallaron 0 · con error 1 · saltadas 0
   ✗ tests.test_exportar: ModuleNotFoundError: No module named 'exportar'

Essa é a sexta-feira da Sofía, na máquina local.

Passo 4. Conserte como o Iván: ponha o arquivo dentro do pacote. Copie só a linha de sed do sistema em uso: o do macOS pede um argumento vazio depois de -i.

mv src/exportar.py src/revisor/exportar.py

# En Linux:
sed -i 's/^from exportar import/from revisor.exportar import/' tests/test_exportar.py

# En macOS:
sed -i '' 's/^from exportar import/from revisor.exportar import/' tests/test_exportar.py

bash herramientas/probar-instalado.sh

Agora sai pruebas: 63 · pasaron 63 e ✅ lo instalado pasa sus pruebas.

Passo 5. Tente enganar o guarda. Peça ao pytest que exija o pacote instalado, a partir do ambiente editável:

REVISOR_EXIGIR_INSTALADO=1 pytest
ERROR: REVISOR_EXIGIR_INSTALADO=1, pero revisor viene de la carpeta src/revisor. Estas pruebas probarían tu carpeta, no lo que se instala. Usa: bash herramientas/probar-instalado.sh

Ele se recusou a dar um verde. Esse é o controle trabalhando.

Passo 6. Desfaça tudo.

git checkout -- . && git clean -fd
bash herramientas/probar-frontera.sh

Atenção: git clean -fd também apaga os arquivos novos criados ali, como anotações.

Como saber que deu certo: foram sete execuções e três desfechos. Verde, cinco vezes: as duas do passo 1, o pytest do passo 3, o passo 4 e a fronteira do passo 6. Vermelho, uma: o pacote instalado no passo 3, com No module named 'exportar'. E a recusa do guarda no passo 5. Se no passo 3 o script sair verde, confira se exportar.py ficou em src/ e não em src/revisor/. Se no passo 4 continuar vermelho, confira se a primeira linha de tests/test_exportar.py diz from revisor.exportar import a_csv.

Para ir mais longe (opcional): acrescente a_csv a reporte.py, some csv às opções de --formato em cli.py e escreva o teste. Antes de rodá-lo, preveja o que probar-instalado.sh vai contar.

Para levar

Três perguntas para fazer ao repositório esta semana.

  1. De onde rodam os testes? Se a resposta for «da raiz, com python -m pytest», que arquivos da raiz o Python consegue ver que não vão no pacote?
  2. Os testes já rodaram alguma vez contra o pacote instalado, num ambiente novo e sem -e? Faça isso uma vez. Se quebrar, é um erro encontrado antes dos usuários.
  3. O projeto está descrito no pyproject.toml, ou ainda é preciso ler um setup.py e um requirements.txt para saber do que ele precisa?

O que vem depois?

No Go, a fronteira é vigiada pela ferramenta. No Python, pela instalação. No Rust, pelo compilador, e ele é mais rigoroso do que se espera.

Duas semanas depois, o Iván pegou um programa em Rust. Toda a lógica vivia num main.rs de 900 linhas. Ele quis escrever um teste de integração, daqueles que vivem fora do código, em tests/.

O Cargo não encontrou nada para importar. O programa funcionava, mas de fora ninguém conseguia mexer nele.

Como se testa uma função que ninguém consegue importar? Na entrega 3: um arquivo chamado lib.rs, e por que o compilador está dando uma pista, não um castigo.

O template e o curso

  • Template: github.com/HabilMX/estructura-python. Pode ser usado como template do GitHub ou clonado [22]. O README explica cada pasta e quando acrescentar cada coisa. O código é MIT; o texto e as explicações são CC BY-SA 4.0.
  • Versões: pede Python 3.12 ou mais novo e foi testado até o 3.14, no macOS e no Linux: com o 3.14.9, o mais recente do 3.14 em 22 de outubro [21], com o 3.12.15 e com o 3.12.3, o do Ubuntu 24.04 [11]. O 3.15 ainda não. O 3.14 tem suporte até outubro de 2030; o 3.12 já só recebe correções de segurança, até outubro de 2028 [20]. Só usa a biblioteca padrão; pytest 9.1.1, ruff 0.17.1 e mypy 2.4.0 vão fixados no grupo de desenvolvimento.
  • Curso de Python da Hábil: se pyproject.toml, python -m ou a instalação editável forem novidade, a lição 7 explica tudo do zero: Curso de Python · Lição 7, Um pacote com linha de comando.

Escrevo a partir do que vemos todos os dias na Hábil, onde construímos sistemas para bancos, seguradoras e empresas no México. Se no trabalho ou na faculdade aparecer um repositório em que ninguém sabe onde mexer, vamos conversar: habil.mx

O que a Hábil faz?

Ensinamos a programar com o mesmo que usamos para construir: código testado, explicado e aberto.

Esta série nasce do mesmo lugar que os nossos cursos. O Curso de Python vai do zero até um primeiro programa completo, e o publicamos aberto, com licença Creative Commons; a lição 7 explica o pacote com linha de comando, o pyproject.toml e a instalação editável que o template deste artigo usa. E no catálogo há mais cursos de programação, gratuitos e com código testado e explicado passo a passo.

Veja o Curso de Python e o catálogo de Cursos de programação.

Se a equipe herdar um sistema em que ninguém sabe onde mexer, ou precisar construir um que possa continuar sendo mantido, também fazemos isso: veja Construir um sistema sob medida. Peça a primeira conversa, sem custo. Nela avaliamos se convém configurar o que a equipe já tem, usar um produto ou construir, e o que ela receberia em cada caso. Escreva para nós pelo WhatsApp.

Fontes consultadas em 22 de outubro de 2026. As citações em inglês são reproduzidas tal como estão.

Referências

  1. Python Packaging Authority, «src layout vs flat layout», Python Packaging User Guide. https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/
  2. pytest, «Good Integration Practices», docs.pytest.org (pytest 9.1). https://docs.pytest.org/en/stable/explanation/goodpractices.html
  3. pytest, «How to invoke pytest»: «Calling pytest through python -m pytest», docs.pytest.org. https://docs.pytest.org/en/stable/how-to/usage.html
  4. pytest, «pytest import mechanisms and sys.path/PYTHONPATH», docs.pytest.org. https://docs.pytest.org/en/stable/explanation/pythonpath.html
  5. pip, «Local project installs»: «Editable installs», pip.pypa.io. https://pip.pypa.io/en/stable/topics/local-project-installs/
  6. Hatch, «Build configuration»: «Packages» e «Dev mode», hatch.pypa.io. https://hatch.pypa.io/latest/config/build/
  7. Hatch, «Wheel builder»: «Default file selection», hatch.pypa.io. https://hatch.pypa.io/latest/plugins/builder/wheel/
  8. Python, «Command line and environment»: opção -m, docs.python.org. https://docs.python.org/3/using/cmdline.html
  9. G. Thomas, M. Klose, F. Laíns, D. Stufft, T. Chung, S. Rivera, E. Hashman e P. Gedam, «PEP 668 – Marking Python base environments as “externally managed”», 18 de maio de 2021. https://peps.python.org/pep-0668/
  10. Linux Mint, «Linux Mint 22.3 Release Notes» («Linux Mint 22.x is based on Ubuntu 24.04»), linuxmint.com. https://www.linuxmint.com/rel_zena.php
  11. Launchpad (Canonical), pacotes publicados no Ubuntu 24.04 «noble»: python3-defaults 3.12.3 e python-pip 24.0. https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=python3-defaults&exact_match=true · https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=python-pip&exact_match=true
  12. Python, «What's New In Python 3.13»: «Improved error messages», docs.python.org. https://docs.python.org/3/whatsnew/3.13.html
  13. Python Packaging Authority, «Writing your pyproject.toml», Python Packaging User Guide. https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
  14. Python Packaging Authority, «install_requires vs requirements files», Python Packaging User Guide. https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/
  15. Brett Cannon, «PEP 751 – A file format to record Python dependencies for installation reproducibility», 24 de julho de 2024 (aceita em 31 de março de 2025). https://peps.python.org/pep-0751/
  16. Stephen Rosen, «PEP 735 – Dependency Groups in pyproject.toml», 20 de novembro de 2023 (aceita em 10 de outubro de 2024). https://peps.python.org/pep-0735/
  17. pip, «Changelog», versão 25.1 (26 de abril de 2025), pip.pypa.io. https://pip.pypa.io/en/stable/news/
  18. Astral, «Creating projects» (uv), docs.astral.sh. https://docs.astral.sh/uv/concepts/projects/init/
  19. Python, «The Python Tutorial», 6.1.2 «The Module Search Path», docs.python.org. https://docs.python.org/3/tutorial/modules.html
  20. Python Developer's Guide, «Status of Python versions». https://devguide.python.org/versions/
  21. Python Software Foundation, «Python 3.14.9», 21 de outubro de 2026. https://www.python.org/downloads/release/python-3149/
  22. GitHub Docs, «Creating a template repository». https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository
  23. GitHub Docs, «Disabling and enabling a workflow». https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows
  24. Python, «Using Python on macOS» (§5.1), docs.python.org. https://docs.python.org/3/using/mac.html · Instaladores: https://www.python.org/downloads/macos/