Pular para o conteúdo principal

Gabarito para Projeto Python Perfeito

Executar os passos para criar um projeto python perfeito toda vez é bem chato e trabalhoso. O melhor a fazer é criar um gabarito com tudo pronto que possa ser instanciado sempre que um novo projeto é necessário. Vamos criar esse gabarito usando Cookiecutter.

Neste artigo, vamos mostrar o processo de criação do gabarito do projeto Python perfeito que pode ser instanciado facilmente a cada novo projeto.

Uso

Instanciar um novo projeto a partir de um gabarito é simples. Você só precisa do cookiecutter_ e do endereço do gabarito do projeto no GitHub:

$ cookiecutter gh:andredias/perfect_python_project

Mas é provável que você ainda não tenha ele instalado. Neste caso, o melhor jeito de usar o cookiecutter através do pipx:

$ pip install --user pipx
$ pipx ensurepath
$ pipx run cookiecutter gh:andredias/perfect_python_project

A primeira linha instala pipx. A segunda insere o caminho no PATH e a terceira instala o cookiecutter temporariamente para executar o comando uma vez só.

Você responde umas perguntas para ajustar as configurações e o projeto é criado em um diretório relativo ao diretório atual.

Funcionamento

Cookiecutter cria um novo projeto copiando uma árvore de diretórios e substituindo todas as ocorrências entre {{ e }} (baseado no Jinja_) pelos valores correspondentes definidos em cookiecutter.json. A substituição acontece inclusive em nome de arquivos e diretórios.

A estrutura comum de um projeto de gabarito baseado no cookicutter é:

perfect_python_project/
├── {{cookiecutter.project_slug}}/
│   └── ...
├── hooks
│   ├── pre_gen_project.sh
│   └── post_gen_project.sh
└── cookiecutter.json
  1. perfect_python_project é o nome do gabarito que estamos criando
  2. {{cookiecutter.project_slug}} é o diretório que será copiado para criar o novo projeto. O nome do projeto será definido pelo usuário durante a execução do comando cookicutter.
  3. O diretório de hooks, os scripts pre_gen_project.sh e post_gen_project.sh são opcionais. Só precisam ser definidos caso haja necessidade de algum processamento antes ou depois da geração do projeto. Veja a documentação sobre pre/post hooks.
  4. O arquivo cookiecutter.json contém os valores iniciais dos ajustes possíveis do gabarito:
{
    "author": "",
    "email": "",
    "project_name": "Project",
    "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
    "python_version": "3.10",
    "version_control": [
        "hg",
        "git"
    ]
}
  1. Criar o projeto perfeito manualmente
  2. Editar as partes configuráveis
  3. Criar arquivo cookiecutter.json com as variáveis de ajuste

Depois de realizar todos os passos da construção do projeto Python perfeito, nós temos a seguinte estrutura de arquivos e diretórios:

.
├── app
│   └── __init__.py
├── .flake8
├── .github
│   └── workflows
│       └── continuous_integration.yml
├── .hgignore
├── Makefile
├── poetry.lock
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_app.py

O passo seguinte é substituir todas as partes configuráveis por declarações no padrão {{ cookiecutter.<nome_variavel> }}. Em pyproject.toml, por exemplo, é possível ajustar várias partes do projeto tais como nome (linha 2), autores (linha 5, versão do Python (linha 12) e tamanho da linha (linhas 30 e 33):

System Message: ERROR/3 (<string> line 127)

Error in "code" directive: unknown option: "emphasize-lines".

.. code:: toml
    :number-lines:
    :emphasize-lines: 2, 5, 12, 30, 33

    [tool.poetry]
    name = "{{cookiecutter.project_name}}"
    version = "0.1.0"
    description = ""
    authors = ["{{ cookiecutter.author }} <{{ cookiecutter.email }}>"]

    [build-system]
    requires = ["poetry-core>=1.0.0"]
    build-backend = "poetry.core.masonry.api"

    [tool.poetry.dependencies]
    python = "^{{ cookiecutter.python_version }}"

    [tool.poetry.dev-dependencies]
    pytest = "*"
    pytest-cov = "*"
    blue = "0.5.2"
    flake8 = "*"
    flake8-debugger = "*"
    flake8-pytest-style = "*"
    isort = "*"
    mypy = "*"
    pep8-naming = "*"
    pyupgrade = "*"
    bandit = "*"
    pip-audit = "*"

    [tool.isort]
    profile = "black"
    line_length = {{ cookiecutter.line_length }}

    [tool.blue]
    line-length = {{ cookiecutter.line_length }}

    [tool.pytest.ini_options]
    filterwarnings = ["ignore::DeprecationWarning"]

    [tool.mypy]
    ignore_missing_imports = true
    disallow_untyped_defs = true

Em .flake8, devemos fazer o tamnho da linha configurável:

System Message: ERROR/3 (<string> line 176)

Error in "code" directive: unknown option: "emphasize-lines".

.. code:: ini
    :emphasize-lines: 2

    [flake8]
    max_line_length = {{ cookiecutter.line_length }}
    exclude = .venv,.mypy_cache,.pytest_cache
    ignore = PT013,PT018,W503

Outros ajustes são necessários em Makefile e .github/workflows/continuous_integration.yml:

System Message: ERROR/3 (<string> line 187)

Error in "code" directive: unknown option: "emphasize-lines".

.. code:: Make
    :emphasize-lines: 5

    ...

    format:
        isort .
        blue .
        pyupgrade --py{{cookiecutter.python_version.replace('.', '')}}-plus **/*.py

System Message: ERROR/3 (<string> line 198)

Error in "code" directive: unknown option: "emphasize-lines".

.. code:: yaml
    :number-lines:
    :emphasize-lines: 14, 29

    name: Continuous Integration

    on: [push]

    jobs:

    lint_and_test:
        runs-on: ubuntu-latest
        steps:

            - name: Set up python
            uses: actions/setup-python@v2
            with:
                python-version: {{cookiecutter.python_version}}

            - name: Check out repository
            uses: actions/checkout@v2

            - name: Install Poetry
            uses: snok/install-poetry@v1
            with:
                virtualenvs-in-project: true

            - name: Load cached venv
            id: cached-poetry-dependencies
            uses: actions/cache@v2
            with:
                path: .venv
                key: venv-{{ "${{ hashFiles('**/poetry.lock') }}" }}

            - name: Install dependencies
            if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
            run: poetry install --no-interaction

            - name: Run tests
            run: poetry run make test

            - name: Build Docker Image
            run: poetry run make build

À medida que as partes configuráveis são identificadas e substituídas, é necessário declarar as variáveis correspondentes no arquivo cookicutter.json, que fica na raiz do projeto do gabarito:

{
    "author": "",
    "email": "",
    "project_name": "Project",
    "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
    "python_version": "3.10",
    "line_length": 79,
    "version_control": [
        "hg",
        "git"
    ]
}

O arquivo .hgignore só serve para projetos que usam Mercurial. Será ajustado em uma próxima etapa, através de um hook de pós geração.

System Message: WARNING/2 (<string> line 274) id2

Duplicate explicit target name: "cookiecutter".

Docutils System Messages

System Message: ERROR/3 (<string> line 17) id4

Duplicate target name, cannot be used as a unique reference: "cookiecutter".

System Message: ERROR/3 (<string> line 47) id6

Unknown target name: "jinja".

Comentários

Comments powered by Disqus