---
# System prepended metadata

title: 16 Application Factory
tags: [web, day2]

---

---
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.