Generate Files Python: O Que É o script generate_files.py e Como Usar

Generate Files Python: O Que É o script generate_files.py e Como Usar

“`html





generate_files.py — um guia prático para gerar arquivos com segurança

Dev • Python • Estrutura de projetos
Prática e confiável

generate_files.py

Um guia técnico (e bem direto ao ponto) para você criar e revisar um script generate_files.py
que gera arquivos de forma previsível, segura e fácil de manter — com boas decisões de caminho, escrita, validação e idempotência.

Objetivo: gerar/atualizar arquivos
Foco: paths, idempotência e validação
Resultado: pipeline local mais confiável

1) Defina o “contrato” do script: entradas, saídas e idempotência

Antes de escrever qualquer linha, eu defino o que generate_files.py realmente entrega.
Isso evita scripts “mágicos” que mudam o repositório sem controle.

Um contrato bem definido normalmente cobre:

  • Entradas: diretório base, templates/strings, lista de arquivos, modo (dry-run, force).
  • Saídas: quais caminhos serão criados/atualizados (por exemplo: src/, tests/, docs/).
  • Idempotência: rodar duas vezes não deve causar diffs desnecessários (conteúdo igual → arquivo igual).
  • Falha segura: se der erro no meio, você não quer um estado “meio gerado” sem aviso.

Dica: se o conteúdo do arquivo é derivado de dados determinísticos (templates fixos, nomes conhecidos), você consegue comparar e só escrever quando muda.

2) Caminhos robustos: use pathlib e restrinja o “root”

Problemas clássicos em geradores surgem quando o script monta caminhos com str e concatenação.
Eu prefiro pathlib e sempre trabalho com um “root” (diretório base) explícito.

Regras que eu sigo:

  • Normalize: converta para Path.resolve() e compare se o destino ainda está dentro do root.
  • Crie diretórios: use mkdir(parents=True, exist_ok=True) apenas no nível necessário.
  • Evite path traversal: se algum nome vem de entrada (mesmo que “confiável”), valide para não sair do diretório base.
  • Compatibilidade: todo caminho deve ser construído com root / "subdir" / arquivo.

3) Escrita com controle: compare conteúdo antes de gravar

Para manter o repositório limpo, eu imponho uma regra: se o conteúdo não mudou, não escreva.
Isso reduz diffs, melhora o comportamento em CI e evita “touch” acidental.

O fluxo típico é:

  • gerar o conteúdo em memória (string) →
  • ler o arquivo existente (se existir) →
  • comparar (mesmo encoding, mesma string) →
  • escrever somente quando necessário.

Além disso, eu prefiro abrir arquivos com encoding explícito (ex.: utf-8) e usar newline="\n"
para manter consistência.

Resultado prático: o script fica idempotente e “barulhento” apenas quando realmente há mudança.

4) Fluxo de execução: validação, logs úteis e modo dry-run

Um gerador bom não é só “funciona”; ele também é diagnosticável.
Eu adiciono logs objetivos e um modo dry-run para visualizar o que seria feito.

Um padrão útil de validação antes de escrever:

  • verifique se os dados de entrada (nomes, templates, listas) não estão vazios;
  • valide extensões permitidas (ex.: .py, .md);
  • valide se a lista de arquivos é única (sem duplicatas que causem sobrescrita acidental);
  • em caso de erro, falhe cedo com mensagens claras sobre qual arquivo/etapa quebrou.

E para logs, eu costumo diferenciar:
gerado/atualizado
mantido (sem mudança)
dry-run (simulado)

Exemplo: escrita idempotente com segurança de paths e dry-run

Aqui vai um núcleo de generate_files.py que eu uso como base:
ele garante que o destino está dentro do root, cria diretórios, compara conteúdo e respeita dry-run.

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path


@dataclass(frozen=True)
class WritePlan:
    rel_path: str
    content: str


def safe_join(root: Path, rel_path: str) -> Path:
    root = root.resolve()
    target = (root / rel_path).resolve()

    # Evita sair do diretório base (path traversal)
    if root not in target.parents and target != root:
        raise ValueError(f"Destino fora do root: {rel_path}")

    return target


def write_if_changed(root: Path, rel_path: str, content: str, *, dry_run: bool = False) -> str:
    target = safe_join(root, rel_path)

    if not target.parent.exists():
        if dry_run:
            # Em dry-run, eu só simulo
            return "dry-run:create-dir"
        target.parent.mkdir(parents=True, exist_ok=True)

    encoding = "utf-8"
    existing = None
    if target.exists():
        existing = target.read_text(encoding=encoding)

    if existing == content:
        return "kept:no-change"

    if dry_run:
        return "dry-run:write"

    target.write_text(content, encoding=encoding, newline="
")
    return "updated:write"


def main() -> None:
    project_root = Path(__file__).resolve().parent
    output_root = project_root / "generated"

    plans = [
        WritePlan(
            rel_path="__init__.py",
            content="\"\"\"Arquivos gerados.\"\"\"\n",
        ),
        WritePlan(
            rel_path="example.py",
            content=(
                "def hello():\n"
                "    return 'ok'\n"
            ),
        ),
    ]

    # Troque para True quando quiser só visualizar
    dry_run = False

    for plan in plans:
        status = write_if_changed(
            output_root,
            plan.rel_path,
            plan.content,
            dry_run=dry_run,
        )
        print(f"{status:>18}  {plan.rel_path}")


if __name__ == "__main__":
    main()

Esse esqueleto resolve os pontos mais comuns:
root seguro, comparação antes de gravar, dry-run e criação de diretórios.

Quer evoluir seu gerador e sua base de código?

Se esse tema te ajudou, lê mais posts por aqui. Eu vou te mostrando padrões práticos para
manter projetos previsíveis, com menos surpresas em CI e diffs mais limpos.

yurideveloper.com • Boas práticas para scripts que mexem em arquivos com segurança e previsibilidade.



“`