Estrutura e organização de pastas em projetos Flask

Estrutura e organização de pastas em projetos Flask

A estrutura final com base nos melhores artigos que encontrei sobre o assunto

Este artigo é para quem sente a necessidade de colocar uma estrutura em seus projetos Flask, principalmente porque queremos que um novo membro no projeto entenda sem muita explicação, queremos uma estrutura que permita que o projeto possa escalar em termos de novas funcionalidades e que também possa ser aplicada em outros projetos.

Confira a lista de links de referência no final, se quiser, deixe seu comentário com alguma dúvida ou ponto que não estou cobrindo.

DISCLAIMER: Flask é um micro framework, é flexível e tem na sua base a liberdade para organizar de formas diferentes conforme as extensões e plugins de cada projeto.

DISCLAIMER II: Este conteúdo é fortemente inspirado nos artigos do Bruno Rocha, links no final do artigo.

É comum ver na documentação relacionado ao Flask, exemplos funcionais de código dentro do app.py (one-file application), ou seja, é possível fazer configurações, definições de endpoints da API, configurações do banco de dados, setup das migrações e o mapeamento relacional das tabelas entre muitas outras coisas em apenas um arquivo, por exemplo:

from flask import Flask
from flask_admin import Admin

app = Flask(__name__)
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'

admin = Admin(app, name='microblog', template_mode='bootstrap3')
app.run()

Código da extensão Flask-Admin

Para aplicações mais básicas ou para uso didático isto é fantástico, mas na prática não queremos centralizar tudo de um projeto em apenas um único arquivo. Se compararmos com o Django, outro framework web no mundo Python, diferentemente do Flask, ele já nos fornece uma estrutura inicial pronta para guiar a organização do projeto. A organização do Django também entende que a nossa aplicação vai crescer em funcionalidades, assim temos o conceito de “apps” para separar em domínios ou contextos do negócio.

Trazendo isto para algo mais concreto, podemos ter em um projeto Pizzaria, uma app para pedidos e outra app para controle de estoque ou custos, cada app tem bem separada as definições de:

  • Rotas: (endpoints/api)
  • A parte de controle (views)
  • A parte que irá gerar as páginas HTML (templates)
  • E finalmente o módulo para acesso e mapeamento do banco de dados (models)

Esta organização é fundamental para escalar a evolução de projetos de software, além de melhorar a manutenção e facilitar a escrita de testes unitários.

Em um projeto Django isto seria algo como

# Estrutura de projetos Django
Pizzaria Django
├── manage.py
├── requirements.txt
├── pedidos
│   ├── urls.py
│   ├── views.py
│   ├── forms.py
│   ├── migrations
│   ├── models.py
│   ├── templates
│   ├── tests
│   └── admin.py
├── estoque
│   ├── urls.py
│   ├── views.py
│   ├── migrations
│   ├── models.py
│   ├── templates
│   ├── tests
│   └── admin.py
└── pizzaria
   ├── settings.py
   ├── urls.py
   ├── asgi.py
   └── wsgi.py

👉 IMPORTANTE: Não estou dizendo para copiar a estrutura do Django ou muito menos que Flask deveria ter isso na sua essência, ao invés disto, estou dizendo que ter um padrão, pode ter suas vantagens para muitos casos de uso. okay?

A documentação oficial do Flask fala de Packages, Blueprints e também de Layouts para organizar melhor projetos maiores. Mas não tem nada além da organização das partes de rotas e dos templates.

Mas por que Flask não vem com uma estrutura bem definida igual ao Django?

Em poucas palavras, por que Flask é um microframework, enquanto o Django é um full stack framework, assim você pode ter um projeto com apenas templates ou outro apenas com API e quem sabe outro com APIs para fazer mil coisas menos buscar dados do banco, ou seja, você vai precisar estruturar com base nos requisitos necessários (plugins/extensões instaladas). Desta forma, Flask NÃO te obrigar organizar desta ou de outra forma está mais para uma vantagem do que uma desvantagem.

Mas então por que estamos querendo forçar uma estrutura em projetos Flask?

  • Quando não precisamos pensar em qual estrutura utilizar, podemos partir diretamente para a implementação e regras de negócio
  • Queremos focar nas funcionalidades que vão gerar valor ao invés de ficar dias trabalhando onde podemos colocar isto ou aquilo
  • Quando mudamos de projeto, seja para uma manutenção pontual de um projeto legado, gastamos muito tempo para lembrar onde está cada coisa. Em contrapartida, quando temos um padrão entre projetos, fica muito mais fácil ser produtivo mesmo em um produto que ficamos meses sem codificar
  • Queremos separar nossa estrutura pensando em responsabilidade de cada camada da aplicação
  • A menos que seu projeto seja um microsservico com dois ou três endpoints apenas, tem grande chances de virar uma bagunça e ficar bem difícil de dar manutenção conforme o projeto vai recebendo novas funcionalidades
  • Se já sabemos que vamos escalar em nível de funcionalidades de diferentes contextos (como no exemplo de app da pizzaria), definir uma estrutura o quanto antes, vai fazer toda diferença para permitir entregas mais rápidas

Que padrões podemos encontrar por aí?

Para começar tem este artigo Digitalocean que apesar de bem popular, eu não gosto muito. Mas vamos iniciar com algo mais interessante, o Flaskr da documentação oficial do Flask

├── flaskr
│   ├── __init__.py             👉 create app do Flask + configurações
│   ├── auth.py                 👉 API, validação, regras de negócio, acesso banco 
│   ├── blog.py                 👉 mesma coisa, mas para blog ao invés de auth  
│   ├── db.py
│   ├── schema.sql              👉 setup de todas as tabelas
│   ├── static
│   │   └── style.css
│   └── templates
│       ├── base.html
│       ├── auth                👉 separa páginas do contexto auth
│       │   ├── login.html
│       │   └── register.html
│       └── blog                👉 separa páginas do contexto blog
│           ├── create.html
│           ├── index.html
│           └── update.html
└── tests
    ├── conftest.py
    ├── data.sql
    ├── test_auth.py
    ├── test_blog.py
    ├── test_db.py
    └── test_factory.py

É interessante que tem uma separação do contexto auth do blog, também existe a separação dentro da camada de templates e nos testes.

Mas acredito que poderia ser um pouco melhor se:

  • Não colocar muita coisa no init
  • Tentar fazer mais separações com base no contexto

Vamos olhar com mais detalhe o código abaixo

# /examples/tutorial/flaskr/blog.py

@bp.route("/create", methods=("GET", "POST"))
@login_required
def create():
   """Create a new post for the current user."""
   if request.method == "POST":
       title = request.form["title"]
       body = request.form["body"]
       error = None

       if not title:
           error = "Title is required."

       if error is not None:
           flash(error)
       else:
           db = get_db()
           db.execute(
               "INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)",
               (title, body, g.user["id"]),
           )
           db.commit()
           return redirect(url_for("blog.index"))

   return render_template("blog/create.html")

Note que temos a definição das rotas com route(“/create”), temos a validação de dados de entrada com if not title, temos também regras de controle e por último, acesso a dados do banco de dados. O ponto aqui é, será que esta parte poderia estar melhor separada? Talvez não deixar tantas responsabilidades em um único lugar.

Outros pontos que geralmente costumo adicionar nos projetos Flask que não estão cobertos na estrutura do tutorial do Flask:

  • Fazer a camada de API não ter muito mais que as definições de rotas e validações de dados de inputs
  • A camada da API fazer o papel de controller delegando para a camada de serviço
  • Versionamento dos dados (migrations)

Existem alguns templates de projetos Flask interessantes, boa parte estão na linha do apresentado acima. Ou seja, algumas camadas têm a separação em contextos (blog, auth, pedidos, estoque) e outras não.

Abaixo segue o template cookiecutter-flask-restful que achei interessante, note as pequenas imagens tentando representar as responsabilidades:

restful_api
├── myapi
│   ├── app.py                    👉 create app do Flask
│   ├── ⚙️ config.py              👉 configurações 
│   ├── ⚙️ extensions.py          👉 db, migrations, jwt etc… (apps plugáveis)
│   ├── 🧅 api
│   │   ├── resources
│   │   │   └── user.py 📦🎂
│   │   ├── schemas
│   │   │   └── user.py
│   │   └── views.py              👉 declarações da blueprints da API
│   ├── auth 📦
│   │   ├── helpers.py 🎂         👉 Regras de autenticação 
│   │   └── 🧅 views.py           👉 API de autenticação (pq está fora de api?)
│   ├── commons
│   │   ├── ⚙️ apispec.py 
│   │   └── 🧅 pagination.py
│   ├── manage.py
│   ├── 🧅 models                 👉 ORM
│   │   ├── blocklist.py 📦
│   │   └── user.py 📦
│   └── wsgi.py
├── requirements.txt
└── tests
   ├── conftest.py
   ├── factories.py
   ├── test_auth.py
   └── test_user.py

Legendas:

⚙️ = Configurações
🧅 = Camada de API, Services, Templates ou Models (Layer)
📦 = Contexto (Pedido, Vendas, Estoque etc...) 
🎂 = Regras de negócio

Apesar de achar esta estrutura interessante, tem um pouco de mistura onde tem coisas de camada e coisas de contexto e talvez o que mais faz falta é uma camada de serviço, ou seja, tem código de negócio dentro da camada de API. Outro indício da falta desta camada é a classe auth/herlpers.py com regras de autenticação (negócio). Note também que o arquivo auth/views.py tem código de API e não está dentro da pasta api como as rotas de user e auth.

Finalmente, vamos para a estrutura que gostaria de recomendar...

Sugestão recomendada para seus projetos Flask:

O projeto está separado:

  • Por 🧅 camadas, as quais não devem mudar muito ao longo do tempo
  • Por módulos de 📦 contextos abaixo de cada camada, os quais contêm os códigos que devem evoluir na implementação
  • Módulos de ⚙️ configurações separadas conforme as extensões ou plugins do Flask
Confraria-News
.
├── hackernews
│   ├── app.py                    👉 Ponto de entrada (create_app)
│   ├── exceptions.py
│   ├── 🧅 ext                    👉 Configurações/Settings
│   │   ├── ⚙️ configuration.py
│   │   ├── ⚙️ api.py
│   │   └── ⚙️ database.py
│   │   ...
│   ├── 🧅 api                    👉 Rotas
│   │   ├── 📦 auth.py
│   │   ├── 📦 news.py
│   │   └── 📦 openapi.yaml       👉 Especificações das rotas + validações
│   │   ...
│   ├── 🧅 services               👉 Regras de negócio
│   │   ├── 📦 auth.py 🎂
│   │   ├── 📦 news.py 🎂
│   │   └── 📦 token.py 🎂
│   │   ...
│   └── 🧅 models                 👉 ORM
│       ├── 📦 news.py
│       └── 📦 users.py
│       ...
├── ⚙️ migrations                 👉 Versionamento do banco
│   ├── alembic.ini
│   ├── env.py
│   ├── script.py.mako
│   └── versions
├── tests
│   ├── conftest.py
│   ├── api                      👉 Teste de rotas, entrada, saída e validações
│   ├── database                 👉 Teste de conexão com o banco
│   └── services                 👉 Teste das regras de negócio
├── requirements.txt
├── pytest.ini
├── uwsgi.ini                    👉 Configurações do serv. de aplicação  
└── wsgi.py                      👉 Arquivo para PROD (Gunicorn/uWSGI)

minions-impressed.gif

Esta estrutura é baseada principalmente no melhor conteúdo que encontrei sobre organização de projetos Flask, o qual tem um título perfeito para o assunto: Arquitetura Definitiva para o Projeto Web Com Python e Flask - Valeu Bruno Rocha! Também tem este outro que fala mais da parte de Factories

Principais pontos da estrutura:

Separação do create_app

Separando o create_app onde tem o código que inicia o framework (app = Flask(__name__)) e os plugins, deixa mais simples criar a instância do projeto de diferentes lugares, como em dev usando Werkzeug, produção usando Gunicorn/uWSGI , testes ou via um CLI

post_pastas_resumo2.jpg

Configurações por ambiente

import os


def init_app(app):
    """Flask init app"""
    config = get_config_from_env()
    app.config.from_object(config)


def get_config_from_env():
    """Carrega configurações do ambiente"""
    envname = os.getenv("FLASK_ENV", "production").lower()
    if envname == "development":
        return DevelopmentConfig()
    return ProductionConfig()


class ProductionConfig:  # pylint: disable=R0903
    """PROD"""

    # FLASK
    FLASK_ENV = "production"
    DEBUG = False
    TESTING = False
    SECRET_KEY = os.getenv("FLASK_SECRET_KEY", "Ch@nG3_ME!")

    # DATABASE
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URI")
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    # AUTH and JWT
    JWT_ISS = os.getenv("JWT_ISS", "https://confrarianews.com.br")


class DevelopmentConfig(ProductionConfig):  # pylint: disable=R0903
    """DEV"""

    FLASK_ENV = "development"
    DEBUG = True

Mais detalhes podem ser encontrados no projeto exemplo que participei junto com o pessoal da Python Pro:

ou você pode iniciar seu projeto com esta estrutura utilizando o template:

Lista completa de Links e referências

Se tiverem mais links interessantes sobre este assunto, por favor, deixe nos comentários