--- tags: web,day2 robots: noindex, nofollow lang: pt-br --- # 16 Application Factory Chegamos até aqui e acredito que já deu para você entender o funcionamento básico do Flask, uma instância `app` que possui 3 contextos e que podemos adicionar configurações, handlers e views (funções que respondem por URLs). E acabamos de ver que para inicializar uma extensão geralmente usamos a classe da extensão como fizemos com `PyMongo` e passamos a `app` Flask como parâmetro. A partir de agora o nosso aplicativo de blog vai começar a tomar forma e ficar maior, portanto precisamos começar a pensar em uma melhor organização de módulos e objetos. O Flask permite que organizemos a aplicação da forma que desejarmos, ele nos dá a liberdade total e por isso as vezes podemos acabar fazendo escolhas ruins que vão nos dar dor de cabeça no futuro. Eu comecei a trabalhar com Flask em 2010, logo quando foi lançado, inclusive neste mesmo ano dei a minha palestra em um grande evento de tecnologia, a Python Brasil 6 na Universidade Federal de Curitiba onde eu abordei justamente o recém nascido Flask. Nesses 12 anos trabalhando com Flask eu aprendi bastante coisa, criei muitas aplicações e até um CMS (framework para gestão de conteúdo) e em 2014 eu **criei** um padrão pro Flask que virou a recomendação da documentação oficial do Flask, chamado de **application factory**. A idéia é simples: o `app` é um objeto que será criado através de uma `factory function` essa função irá criar a instância de `app` e repassar este objeto para outras funções e classes, permitindo a mutabilidade e no final teremos uma `app` completamente **fabricada** e pronta para servir. Para facilitar o entendimento, o app factory funciona mais ou menos assim: **Pseudo código** ```python # main factory app = funcao_que_cria_um_app() # sub factories adiciona_configuracoes(app) adiciona_banco_de_dados(app) adiciona_plugin_x(app) adiciona_autenticação(app) registra_plugins(app) app.run() # app pronta para rodar ``` **Imagem** ![](https://github.com/rochacbruno/python-web-api/raw/day2/docs/day2/imgs/factories.png) **Mas qual a vantagem em usar este padrão?** Imagine que seu programa tem muitas **views** e componentes, a tendência é que você comece a separar em módulos diferentes e então importar os objetos necessários no módulo principal `app`. acontece que fazendo isso ocorre um dos principais problemas enfrentados por quem programa com Flask, o **circular import error** ![](https://github.com/rochacbruno/python-web-api/raw/day2/docs/day2/imgs/circular_import.png) Para evitar o problema de circular imports existe uma regra muito importante: **Nunca devemos importar `app`** mas sim uma factory capaz de criar o `app` ```diff - from app import app + from app import create_app ``` Vamos ver isso na prática. ### Organizando a aplicação com application factory Vamos então refatorar a nossa aplicação usando o conteito de application factory, vamos começar criando uma pasta chamada `exemplos/day2/flask/blog` e movemos o `app.py` para dentro dela. ```bash mv exemplos/day2/flask/app.py exemplos/day2/flask/blog/app.py ``` E agora alteramos a linha de comando para executar adicionando o caminho correto a variavel `FLASK_APP` > Se preferir pode dar `export` nas variaveis de ambiente e então usar apenas `flask run` ```diff - FLASK_APP=exemplos/day2/flask/app.py FLASK_ENV=development flask run + FLASK_APP=exemplos/day2/flask/blog/app.py FLASK_ENV=development flask run ``` Agora vamos reestruturar o modulo principal da aplicação separando em multiplos módulos, porém evitando circular imports com o uso de sub factories. Vamos criar alguns arquivos: - `auth` - aqui vamos definir a autenticação de usuários - `admin` - aqui vamos criar uma interface administrativa - `config` - aqui vamos carregar as configurações do aplicativo - `commands` - aqui vamos adicionar comandos ao `flask` CLI no terminal - `database` - aqui vamos definir a conexão com banco de dados - `posts` - aqui vamos definir tudo referente a postagens do nosso blog - `plugins` - aqui vamos carregar plugins extras - `__init__` - define que `blog` é um módulo Python No Linux oou mac podemos criar os arquivos em uma única linha de comando: ```bash touch exemplos/day2/flask/blog/{config,database,posts,auth,admin,commands,plugins,__init__,views}.py ``` Precisaremos também de uma pasta para os templates onde criaremos alguns templates: - `base.html.j2` - Template base para todos os outros - `index.html.j2` - Página inicial com a listagem de posts - `post.html.j2` - Página de cada post - `form.html.j2` - Página para cadastro de novas postagens - `error.html.j2` - Página de erros ```bash mkdir exemplos/day2/flask/blog/templates touch exemplos/day2/flask/blog/templates/{base,index,post,form,error}.html.j2 ``` > DICA: no VSCode pode instalar a extensão **Better Jinja** para ajudar com os templates. E criaremos um arquivo para armazenar as configurações default ```bash touch exemplos/day2/flask/blog/settings.toml ``` Um passo muito importante é transformar a aplicação em um pacote Python instalável, isso irá facilitar para resolver os imports. ```bash touch exemplos/day2/flask/setup.py ``` E essa será a estrutura final das pastas da aplicação: ```bash exemplos/day2/flask ├── blog │ ├── admin.py │ ├── app.py │ ├── auth.py │ ├── commands.py │ ├── config.py │ ├── database.py │ ├── __init__.py │ ├── plugins.py │ ├── posts.py │ ├── views.py │ ├── settings.toml │ └── templates │ ├── base.html.j2 │ ├── error.html.j2 │ ├── form.html.j2 │ ├── index.html.j2 │ └── post.html.j2 └── setup.py ``` ## Vamos criar a aplicação usando esta nova estrutura Começando pelo `setup.py` que vai definir a instalação e dependencias, vamos fazer da maneira mais simples possível para não precisar entrar em detalhes sobre empacotamento e gestão de dependencias em Python. ### Instalação > **NOTA** Se você preferir pode ficar a vontade para usar **Poetry**, **PDM** ou qualquer outro gestor de pacotes que preferir. **setup.py** ```python from setuptools import setup setup( name="flask_blog", version="0.1.0", packages=["blog"], install_requires=["flask", "flask-pymongo", "dynaconf"] ) ``` Com isso já podemos instalar o projeto ```bash cd exemplos/day2/flask pip install -e . ... Successfully installed dynaconf-3.1.9 flask-blog-0.1.0 ``` Ao abrir um `ipython` já será possível visualizar os pacotes instalados. ```python from blog import <TAB> admin commands plugins __doc__ __package__ app config posts __file__ auth database templates __name__ ``` ### Main Factory Agora vamos editar o arquivo `app.py` e aplicar o conceito de Application Factory, ao invés de criarmos diretamente uma instância de `app` vamos criar uma função chamada `create_app` `exemplos/day2/flask/blog/app.py` ```python from flask import Flask def create_app(): app = Flask(__name__) return app ``` E de volta ao terminal podemos executar a aplicação com: ```bash FLASK_APP=blog.app:create_app FLASK_ENV=development flask run ``` > **DICA** o mesmo funciona com os subcomandos `routes` e `shell` e você pode persistir as variáveis de ambiente exportando para o ambiente. repare na linha `FLASK_APP=blog.app:create_app`, agora informamos ao flask que deve usar uma função para iniciar uma instância da aplicação e dessa forma evitamos os problemas de circular imports e a aplicação fica muito mais fácil de estender pois podemos seguir com o padrão factory em nossos outros módulos. ### Configurações Para organizar as configurações vamos utilizar a lib `dynaconf` que permite separar as configurações da aplicação Flask e para começar vamos criar no arquivo `config.py` uma função chamada `configure` que será responsável por inicializar as configurações. `exemplos/day2/flask/blog/config.py` ```python import os from dynaconf import FlaskDynaconf HERE = os.path.dirname(os.path.abspath(__file__)) def configure(app): FlaskDynaconf(app, extensions_list="EXTENSIONS", root_path=HERE) ``` Para inicializar a `FlaskDynaconf` usaremos a prática de **inversão de controle**, repare que ao invés de fazer `from app import app` nós criamos uma função `configure` que recebe `app` como injeção de dependencia, e isso é feito para evitar problemas de circular imports. Agora precisamos alterar mais uma vez o arquivo `app.py` `exemplos/day2/flask/blog/app.py` ```python from flask import Flask from .config import configure def create_app(): app = Flask(__name__) configure(app) return app ``` A partir de agora a extensão `Dynaconf` vai buscar as configurações do Flask no arquivo `settings.toml` e podemos nele definir as nossas configurações de maneira mais organizada. ### Database Vamos iniciar o banco de dados em outro módulo, o `blog.database` porém não vamos em NENHUM momento fazer `from app import app` pois isso iria gerar o problema de circular import, ao invés disso vamos utilizar a **Inversão de Controle** através de uma factory funcion que por convenção vamos chamar de `configure` (algumas extensões chamam de `init_app`) Aqui é que está o **Pulo do Gato** o nosso objeto `mongo` que vai apontar a nossa conexão com o banco de dados será um objeto de inicialização **lazy** e só será inicializado quando a função `configure` for invocada. `exemplos/day2/flask/blog/database.py` ```python from flask_pymongo import PyMongo mongo = PyMongo() def configure(app): mongo.init_app(app) ``` Agora alteramos o `settings.toml` para incluir a nossa extensão `database` `exemplos/day2/flask/blog/settings.toml` ```toml [default] mongo_uri = "mongodb://localhost:27017/blog" extensions = [ "blog.database:configure" ] ``` Para garantir que a configuração do banco de dados funcionou pode entrar em um shell e efetuar uma consulta. > **DICA** instale a extensão Flask Shell Ipython `flask-shell-ipython` ```bash FLASK_APP=blog.app:create_app FLASK_ENV=development flask shell ``` ```python In [1]: from blog.database import mongo In [2]: list(mongo.db.posts.find()) Out[2]: [{'_id': ObjectId('62b613a07e4b3c31107abd2b'), 'title': 'Meu primeiro post', 'content': 'Este é meu primeiro blog post', 'published': True}] ``` Agora podemos seguir com a configuração dos outros módulos.