Como realizar migrações Flask-SQLAlchemy usando Flask-Migrate
O autor selecionou a Apache Software Foundation para receber uma doação como parte do programa Write for DOnations.
Introdução
Flask é uma estrutura web Python leve que fornece ferramentas e recursos valiosos para a criação de aplicativos web na linguagem Python. SQLAlchemy é um kit de ferramentas SQL que oferece acesso a banco de dados relacional eficiente e de alto desempenho. Ele fornece maneiras de interagir com diversos mecanismos de banco de dados, como SQLite, MySQL e PostgreSQL. Dá acesso às funcionalidades SQL do banco de dados. Ele também fornece um Mapeador Relacional de Objetos (ORM), que permite fazer consultas e manipular dados usando objetos e métodos Python simples. Flask-SQLAlchemy é uma extensão Flask que facilita o uso do SQLAlchemy com Flask, fornecendo ferramentas e técnicas para interagir com seu banco de dados em seus aplicativos Flask por meio do SQLAlchemy.
Flask-Migrate é uma extensão Flask baseada na biblioteca Alembic, permitindo gerenciar migrações de banco de dados.
A migração de banco de dados é a transferência de dados entre diferentes esquemas de banco de dados sem qualquer perda de dados. É comumente usado para atualizar um banco de dados, alterar seu esquema adicionando novas colunas de tabela ou relacionamentos e garantir uma transição tranquila com tempo de inatividade mínimo e sem perda de dados.
Por exemplo, se você tiver uma tabela chamada Produto
com uma lista de nomes de produtos e quiser adicionar uma coluna de preço a essa tabela, poderá usar a migração do banco de dados para adicionar a coluna de preço sem perder os dados existentes do produto. .
Neste tutorial, você usará Flask-Migrate com Flask-SQLAlchemy para realizar migrações de esquema de banco de dados para modificar suas tabelas e preservar dados.
Pré-requisitos
Para um ambiente de programação local Python 3, siga o tutorial para sua distribuição na série Como instalar e configurar um ambiente de programação local para Python 3. Neste tutorial, chamaremos nosso diretório de projeto de
flask_app
.Uma compreensão dos conceitos básicos do Flask, como rotas, funções de visualização e modelos. Se você não estiver familiarizado com o Flask, consulte Como criar seu primeiro aplicativo da Web usando Flask e Python e Como usar modelos em um aplicativo Flask.
Uma compreensão dos conceitos básicos de HTML. Revise nossa série de tutoriais Como construir um site com HTML.
Entenda os conceitos básicos do Flask-SQLAlchemy, como configurar um banco de dados, criar modelos de banco de dados e inserir dados no banco de dados. Consulte Como usar o Flask-SQLAlchemy para interagir com bancos de dados em um aplicativo Flask para obter conhecimento prévio.
Etapa 01 - Instalando os pacotes Python necessários
Nesta etapa, você instalará os pacotes necessários para seu aplicativo.
No diretório flask_app
, ative seu ambiente virtual:
source <my_env>/bin/activate
Com seu ambiente virtual ativado, use pip
para instalar Flask, Flask-SQLAlchemy e Flask-Migrate:
pip install Flask Flask-SQLAlchemy Flask-Migrate
Assim que a instalação for concluída, a saída imprimirá uma linha semelhante a esta:
Successfully installed Flask-3.0.0 Flask-Migrate-4.0.5 Flask-SQLAlchemy-3.1.1 Jinja2-3.1.2 Mako-1.3.0 MarkupSafe-2.1.3 Werkzeug-3.0.1 alembic-1.12.1 blinker-1.7.0 click-8.1.7 greenlet-3.0.1 itsdangerous-2.1.2 sqlalchemy-2.0.23 typing-extensions-4.8.0
Com os pacotes Python necessários instalados, você configurará um banco de dados e um modelo de exemplo a seguir.
Passo 02 - Configurando um banco de dados e modelo de exemplo
Nesta etapa, você configurará seu aplicativo Flask e um banco de dados Flask-SQLAlchemy com um modelo representando uma tabela de produtos onde você armazenará os produtos de sua loja.
No diretório flask_app
, abra um novo arquivo chamado app.py.
Ele conterá o código principal do seu aplicativo Flask:
nano app.py
Adicione o seguinte código a ele:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)
class Product(db.Model):
__tablename__ = "products"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
def __repr__(self):
return f"<Product {self.name}>"
@app.route("/")
def index():
return "<h1>Hello, World!</h1>"
Salve e feche o arquivo.
Você importa a classe Flask
e SQLAlchemy
dos módulos flask
e flask_sqlalchemy
aqui. Em seguida, você cria uma instância do aplicativo Flask chamada app
.
Em seguida, você define o URI do banco de dados SQLite na configuração do aplicativo; isso especifica o nome do arquivo do banco de dados como app.db
. Este arquivo de banco de dados será criado em uma nova pasta chamada instance,
que será gerada pelo Flask no diretório principal flask_app
.
Em db=SQLAlchemy(app),
você cria uma instância SQLAlchemy, db,
ignorando o aplicativo Flask como argumento.
A seguir, você cria uma classe Product
que herda de db.Model
, representando uma tabela de banco de dados chamada ‘products’. Na tabela, você define uma coluna id
para o ID do produto e uma coluna name
para o nome do produto.
A função especial repr permite que você forneça a cada objeto uma representação de string para reconhecê-lo para fins de depuração.
Por fim, você cria uma rota ('/')
que retorna uma resposta HTML simples ("
quando o URL raiz é acessado.Hello, World!
")
Execute o seguinte comando para testar se o aplicativo está configurado corretamente. Isso executa o aplicativo Flask app
em um servidor de desenvolvimento com depuração ativada:
flask --app app run --debug
Depois de executar este comando, você receberá a seguinte saída:
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Please do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 633-501-683
Isso fornece informações sobre seu aplicativo enquanto ele é executado.
Com o servidor de desenvolvimento em execução, visite o seguinte URL usando seu navegador:
http://127.0.0.1:5000/
Você receberá um cabeçalho <h1>
, ‘Hello, World!`. Isso confirma que o aplicativo está configurado corretamente. Agora você pode passar para a próxima etapa e adicionar Flask-Migrate ao seu aplicativo.
Passo 03 - Adicionando Flask-Migrate à sua aplicação
Nesta etapa, você modificará o arquivo do aplicativo app.py
para adicionar o Flask-Migrate e usá-lo para gerenciar seu banco de dados Flask-SQLAlchemy.
Primeiro, abra app.py
para modificação:
nano app.py
Modifique o arquivo para que tudo acima da declaração da classe Product
fique como segue:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
migrate = Migrate(app, db)
class Product(db.Model):
Salve e feche o arquivo.
Você importa a classe Migrate
do pacote flask_migrate
.
Após a declaração db
, você usa a classe Migrate
para iniciar uma instância de migração chamada migrate,
passando-a para o app
Flask. e a instância do banco de dados db
.
Flask-Migrate fornece um auxiliar de comando flask db
para gerenciar seu banco de dados.
Para concluir a configuração do Flask-Migrate e adicionar suporte ao seu projeto atual, use o seguinte comando no diretório flask_app
:
flask db init
Você receberá uma saída semelhante a esta:
[secondary_label Output]
Creating directory 'flask_app/migrations' ... done
Creating directory 'flask_app/migrations/versions' ... done
Generating flask_app/migrations/README ... done
Generating flask_app/migrations/script.py.mako ... done.
Generating flask_app/migrations/env.py ... done
Generating flask_app/migrations/alembic.ini ... done
Please edit the configuration/connection/logging settings in
'flask_app/migrations/alembic.ini' before proceeding.
Isso cria um novo diretório migrations
dentro da sua pasta flask_app
, onde todos os scripts de migração que gerenciam suas migrações serão armazenados.
Se você conhece o Alembic e deseja adicionar configurações avançadas ao seu sistema de migração de banco de dados, você pode modificar o arquivo migrations/alembic.ini
gerado. Para nossos propósitos, deixaremos como está.
Observação: o diretório migrations
contém arquivos que gerenciam as migrações de banco de dados do seu aplicativo e eles devem ser adicionados ao seu repositório de controle de versão com o restante do código do seu aplicativo.
Com o Flask-Migrate
conectado à sua aplicação, você realizará uma migração inicial do banco de dados. Usar a classe 'Produto' criará a tabela de produtos que você declarou anteriormente.
Criando a tabela de produtos usando um script de migração
Agora você realizará sua primeira migração, criando a tabela Produtos
do seu banco de dados.
No diretório flask_app
, execute o seguinte comando. Este comando flask db migrate
detecta todas as novas tabelas ou modificações que você executa em seus modelos de banco de dados Flask-SQLAlchemy. O sinalizador -m
permite especificar uma mensagem curta descrevendo a modificação realizada:
flask db migrate -m "initial migration"
Você receberá a seguinte saída:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'products'
Generating
flask_app/migrations/versions/b9198aca3963_initial_migration.py
...done
Como nosso banco de dados ainda não foi criado, esta saída informa que uma nova tabela chamada produtos
foi detectada e um script de migração chamado b9198aca3963 inicial_migration.py
foi criado dentro de um diretório chamado versions,
onde todas as diferentes versões de migração são armazenadas.
A parte b9198aca3963
no nome do arquivo do script de migração é um ID aleatório gerado para identificar diferentes migrações para que seja diferente para você.
Para entender como funciona um script de migração, abra o seu com o seguinte comando. Certifique-se de substituir b9198aca3963
pelo ID que foi gerado para você e que você está em seu diretório raiz flask_app
:
nano migrations/versions/b9198aca3963_initial_migration.py
Além das importações internas e de uma configuração, este script de migração inicial terá duas funções principais semelhantes às seguintes:
def upgrade():
# ### commands auto-generated by Alembic - please adjust! ###
op.create_table('products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto-generated by Alembic - please adjust! ###
op.drop_table('products')
# ### end Alembic commands ###
A função
upgrade()
altera o banco de dados com base nas modificações detectadas pelo comandoflask db migrate
. Neste caso, uma nova tabela chamadaprodutos
foi detectada, então a funçãoupgrade()
cria a nova tabela com as colunas especificadas emProduto()
modelo de banco de dados que você declarou no arquivoapp.py
.A função
downgrade()
remove as alterações e restaura o estado do banco de dados como estava antes da atualização. Neste exemplo, o estado anterior é restaurado excluindo a tabelaprodutos
que será criada pela funçãoupgrade()
.
Observação: lembre-se de que os scripts de migração são códigos gerados automaticamente, que podem conter alguns erros dependendo da complexidade das alterações realizadas antes da migração. Portanto, você deve ler e ajustar cuidadosamente seus scripts de migração para garantir a precisão e a execução adequada.
Com o script de migração pronto, agora você pode usá-lo para realizar uma atualização inicial. Isso criará o arquivo de banco de dados app.db
e a tabela products
. Para fazer isso, execute o seguinte comando no diretório flask_app
:
flask db upgrade
A saída será semelhante à seguinte:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> b9198aca3963, initial migration
Isso informa que a atualização foi executada com sucesso.
Um novo arquivo de banco de dados app.db
será adicionado à sua pasta instance
dentro do diretório flask_app
.
Nota: A primeira migração é equivalente a usar db.create_all()
no shell Flask.
Se você introduzir o Flask-Migrate em um projeto existente com um banco de dados existente, então o flask db upgrade
falhará porque já existe um arquivo de banco de dados. Nesse caso, use flask db stamp
para marcar o banco de dados como atualizado em vez de flask db upgrade.
Com o banco de dados e a tabela produtos
criados, agora você pode adicionar alguns produtos ao seu banco de dados. Você fará isso a seguir.
Preenchendo o banco de dados
Agora você usará o shell Flask para inserir alguns itens na tabela products
.
Com seu ambiente virtual ativado, execute o seguinte comando para acessar o shell Flask:
flask shell
Dentro do shell interativo, execute o seguinte código:
from app import db, Product
apple = Product(name="Apple")
orange = Product(name="Orange")
banana = Product(name="Banana")
db.session.add_all([apple, orange, banana])
db.session.commit()
Este código faz o seguinte:
- Importa o objeto
db
e o modeloProduct
do arquivoapp.py
. - Cria três instâncias da classe
Product()
passando um nome para cada produto. - Adiciona os novos produtos à sessão do banco de dados usando o método
db.session.add_all()
. - Aplica alterações ao banco de dados usando o método
db.session.commit()
.
Para obter mais informações sobre como usar o Flask-SQLAlchemy, consulte Como usar o Flask-SQLAlchemy para interagir com bancos de dados em um aplicativo Flask.
Saia do shell do Flask:
exit()
Para garantir que os itens do produto foram adicionados ao banco de dados, reinicie o shell Flask com uma memória nova:
flask shell
Em seguida, execute o seguinte código para percorrer os itens da tabela de produtos:
from app import db, Product
for p in Product.query.all():
print(p.name, p.id)
Aqui, você importa o objeto db
e o modelo Produto
; então você usa um loop for
no resultado do método query.all()
para acessar cada item na tabela de produtos e imprimir o nome e ID de cada item.
A saída será a seguinte:
Apple 1
Orange 2
Banana 3
Você migrou seu banco de dados com êxito e preencheu a tabela de produtos com três itens. Modificaremos o esquema do banco de dados e adicionaremos uma nova coluna price
à tabela products
e, em seguida, migraremos e atualizaremos nosso banco de dados preservando esses dados usando Flask-Migrate.
Passo 04 - Modificando o Modelo de Banco de Dados
Agora você tem uma tabela de produtos em seu banco de dados com alguns itens armazenados nela. Nesta etapa, você usará o Flask-Migrate para adicionar uma coluna price
à tabela products
. Suponha que você queira fazer isso sem um gerenciador de migração de banco de dados. Nesse caso, você deve primeiro excluir toda a tabela produtos
e criá-la novamente com a nova coluna, resultando na perda de todos os itens de produtos existentes. No entanto, com Flask-Migrate, você pode adicionar uma nova coluna enquanto preserva os dados existentes.
Adicionando uma nova coluna de preço
Para adicionar uma coluna de preço à sua tabela de produtos, abra o arquivo app.py
e modifique o modelo de banco de dados Product
que define o esquema da tabela.
Dentro do diretório flask_app
, abra app.py
para modificação:
nano app.py
Edite a classe Product()
adicionando uma nova coluna inteira chamada price
:
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
price = db.Column(db.Integer)
def __repr__(self):
return f'<Product {self.name}>'
Salve e feche o arquivo.
A modificação do esquema do banco de dados foi concluída. Você gerará um novo script de migração para aplicá-lo ao banco de dados e, ao mesmo tempo, evitar a perda de dados.
Gerar uma migração
Depois de modificar seu modelo de banco de dados, execute o comando flask db migrate
para Flask-Migrate detectar sua modificação e gerar um novo script de migração. Certifique-se de adicionar uma mensagem descrevendo o que você modificou:
flask db migrate -m "add price column"
Semelhante à migração inicial, você receberá uma saída como esta:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'products.price'
Generating
flask_app/migrations/versions/7ad34929a0f2_add_price_column.py
... done
Isso informa que uma nova coluna foi detectada e um script de migração foi criado.
Lembre-se de revisar o script de migração gerado. Neste caso as funções principais upgrade()
e downgrade()
serão assim:
def upgrade():
# ### commands auto-generated by Alembic - please adjust! ###
with op.batch_alter_table('products', schema=None) as batch_op:
batch_op.add_column(sa.Column('price', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto-generated by Alembic - please adjust! ###
with op.batch_alter_table('products', schema=None) as batch_op:
batch_op.drop_column('price')
# ### end Alembic commands ###
Aqui, a função upgrade()
altera a tabela produtos
e adiciona uma coluna preço
. A função downgrade()
remove a coluna price()
, restaurando seu banco de dados para a versão anterior.
Atualizando o banco de dados
Após modificar seu modelo de banco de dados, gere um script de migração baseado nesta modificação. Agora você pode aplicar as alterações ao banco de dados usando o comando upgrade
, que executa a função upgrade()
no script de migração mais recente.
flask db upgrade
A saída informará que o banco de dados foi movido da versão anterior para uma nova versão com a "adicionar coluna de preço"
como uma mensagem de migração.
Para testar se a coluna price
foi adicionada, execute o shell Flask:
flask shell
Em seguida, percorra os itens do produto no banco de dados e imprima os valores das colunas:
from app import db, Product
for p in Product.query.all():
print(p.name, p.id, p.price)
A saída deve ser a seguinte:
Apple 1 None
Orange 2 None
Banana 3 None
Os valores None
na saída refletem os valores da nova coluna de preço e agora você pode modificá-los conforme desejar para refletir os preços de seus produtos.
Se você receber um erro ao executar o loop anterior, a coluna de preço não foi adicionada corretamente e você deve revisar cuidadosamente as etapas anteriores, garantindo que executou corretamente todas as ações necessárias.
Saia do shell do Flask:
exit()
O banco de dados agora foi migrado para uma nova versão. A seguir, você fará o downgrade do banco de dados e removerá a coluna price
para demonstrar como restaurar um estado anterior do banco de dados.
Passo 05 - Downgrade do Banco de Dados
Na etapa anterior, você atualizou a versão inicial do banco de dados e adicionou uma coluna price
à tabela products
. Para demonstrar como restaurar um estado anterior ao gerenciar migrações de banco de dados, você fará o downgrade do seu banco de dados atual e removerá a coluna price
da tabela products
.
Para fazer downgrade do seu banco de dados e restaurar sua versão anterior, execute o seguinte comando dentro do diretório flask_app
:
flask db downgrade
A saída informará que a versão do banco de dados foi alterada e a versão anterior foi restaurada.
Para testar se a coluna price
foi removida, abra seu shell Flask:
flask shell
Em seguida, execute uma consulta no modelo Product
para obter todos os itens da tabela de produtos:
from app import db, Product
Product.query.all()
Você deverá receber um erro indicando que a tabela produtos
não tem coluna preço
:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: products.price
[SQL: SELECT products.id AS products_id, products.name AS products_name, products.price AS products_price
FROM products]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Isso confirma que a coluna price
foi removida com sucesso e que o downgrade do banco de dados foi bem-sucedido, e o erro ocorre porque você ainda tem uma coluna price
definida no Product( )
dentro do arquivo app.py
.
Para corrigir esse erro, saia do shell do Flask:
exit()
Em seguida, abra app.py
dentro do diretório flask_app
:
nano app.py
Modifique o modelo Product()
removendo a declaração da coluna price
para que a versão final fique assim:
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
def __repr__(self):
return f'<Product {self.name}>'
Salve e feche o arquivo.
Para testar se o erro foi corrigido, abra o shell do Flask:
flask shell
Em seguida, consulte todos os produtos:
from app import db, Product
Product.query.all()
A saída deve ser a seguinte:
[<Product Apple>, <Product Orange>, <Product Banana>]
Isso indica que o erro foi corrigido.
Finalmente, você pode excluir o arquivo de migração que contém add_price_column
em seu nome de arquivo se não precisar mais dele:
rm migrations/versions/7ad34929a0f2_add_price_column.py
Com isso, você fez o downgrade do seu banco de dados com sucesso e o restaurou para sua versão anterior.
Conclusão
Você criou um pequeno aplicativo Flask com um banco de dados e o integrou ao Flask-Migrate. Você aprendeu a modificar seus modelos de banco de dados, atualizá-los para uma nova versão e fazer downgrade deles para uma versão anterior.
Em geral, você pode seguir as seguintes etapas para gerenciar as migrações de banco de dados à medida que desenvolve seus aplicativos Flask:
Modifique os modelos de banco de dados.
Gere um script de migração com o comando
flask db migrate
.Revise o script de migração gerado e corrija-o, se necessário.
Aplique as alterações ao banco de dados com o comando
flask db upgrade
.Para restaurar uma versão anterior do banco de dados, use o comando
flask db downgrade
.
Consulte a documentação do Flask-Migrate para obter mais informações.
Se você quiser ler mais sobre o Flask, confira os outros tutoriais da série Como construir aplicativos da Web com Flask.