How to configure model to not create tables in flask sqlAlchemy? - python

How to configure sqlAlchemy to not create tables in flask sqlAlchemy?
i only use flask-sqlalchemy Model's features,but won't create table.
How to configure this?
because i want to use in sql View,if run db.create_all(),View become to table.
thanks.
this is a View!
# -*- coding: utf-8 -*-
from settings.dataBase import CRUDMixin, db
class ViewExample(db.Model, CRUDMixin):
__tablename__ = "view_example"
id = db.Column(db.Integer, primary_key=True, nullable=False)
name = db.Column(db.String(50))
title = db.Column(db.String(100))
is_albums = db.Column(db.Integer)
is_attach = db.Column(db.Integer)
is_spec = db.Column(db.Integer)
sort_id = db.Column(db.Integer)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def __str__(self):
return 'id : %s' % self.id
i find a example,but i dont know how to use in flask-sqlalchemy.
alembic slqlalchemy document:
https://alembic.sqlalchemy.org/en/latest/cookbook.html#don-t-emit-create-table-statements-for-views
flask-migrate env.py
i add a include_object func ,but dont get is_view.
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option(
'sqlalchemy.url', current_app.config.get(
'SQLALCHEMY_DATABASE_URI').replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def include_object(object, name, type_, reflected, compare_to):
"""
Exclude views from Alembic's consideration.
"""
print(object.info)
return not object.info.get('is_view', False)
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object,# add
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
add __table_args__ = { "info": dict(is_view=True)} is good.
class ViewExample(db.Model, CRUDMixin):
__tablename__ = "view_example"
__table_args__ = { "info": dict(is_view=True)} # add
thanks! this questions been solved.

if you are not using alembic, the answer given here is not full.
what you need to do is change the metadata class that comes with base.Model in the following way:
class ViewAwareMetaData(MetaData):
def __init__(self):
super(ViewAwareMetaData, self).__init__()
def create_all(self, bind=None, tables=None, checkfirst=True):
tables = [table for table in tables if not table.info.get('is_view', False)]
super().create_all(bind=bind, tables=tables, checkfirst=checkfirst)
and then use the new meta when initializaing sqlalchemy:
db = SQLAlchemy(engine_options=engine_options, metadata=ViewAwareMetaData())

You can add __abstract__ = True in the model that your don't want to create

Related

alembic autogenerates creates empty migration but generates table

I followed the documentation from Alembic to auto-generate migrations. My project structure looks like this:
alembic/
versions/
env.py
README
script.py.mako
data/
__init__.py
db.py
models.py
alembic.ini
app.db
I made changes to env.py by exactly following the document:
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from data.models import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
This is my __init__.py:
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from .models import Base
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
db_url = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
engine = create_engine(db_url, echo=False)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)
I created a User class in models.py like this:
from sqlalchemy import Column, Sequence, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, Sequence('id_seq'), primary_key=True)
first_name = Column(String, nullable=False)
last_name = Column(String, nullable=False)
def __repr__(self):
return "<%s('%s', '%s')>" \
% (self.__class__.__qualname__, self.first_name,
self.last_name)
After that, I run migration by:
alembic revision --autogenerate -m "Added user table"
However, I got empty upgrade() and downgrade() in migration file:
"""Added user table
Revision ID: 279933caec54
Revises:
Create Date: 2021-05-12 16:21:05.772568
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '279933caec54'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
But when I checked the database, the user table was created there (I didn't do it myself). How did it happen? How did alembic create empty migration but generating the table?
I found the answer in this question. The problem described in this question is not the same as mine (the alembic didn't create an empty migration file in this case) though. That's why I asked the question here (may be seen as duplicated).
So as the answer suggested, I commented the Base.metadata.create_all(engine) in the __init__.py. It is this line of code that creates the user table (if it not exist). So when alembic checks my database, the table is already there. Since no differences are found, the upgrade() and downgrade() are empty.
in this situation don't import your Base model from base file, import your Base from some where you have extend this Base class
for example I have a structure like below:
also i imported Base class in alembic/.env from models.trade.
in this form your base class metadata will detect your models and your auto generation migrations will work fine.

What is the best way to populate a table in SQLAlchemy?

I'm trying to create a list of possible roles of a web app user. If I define the roles table in this way:
roles = db.Table(
"roles",
db.Model.metadata,
db.Column("role_id", db.Integer, db.ForeignKey("task.id"), primary_key=True),
db.Column("name", db.String(32)),
)
What is the best method to populate it if I intend to only do that once (on database creation), and then never add any more rows to it?
I believe this paradigm is called "database seeding", this might help you when you are googling for answers.
I had a look online and found this:
https://pypi.org/project/Flask-Seeder/
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_seeder import FlaskSeeder
def create_app():
app = Flask(__name__)
db = SQLAlchemy()
db.init_app(app)
seeder = FlaskSeeder()
seeder.init_app(app, db)
return app
Then you can create a another file with your seeds.
from flask_seeder import Seeder, Faker, generator
# SQLAlchemy database model
class User(Base):
def __init__(self, id_num=None, name=None, age=None):
self.id_num = id_num
self.name = name
self.age = age
def __str__(self):
return "ID=%d, Name=%s, Age=%d" % (self.id_num, self.name, self.age)
# All seeders inherit from Seeder
class DemoSeeder(Seeder):
# run() will be called by Flask-Seeder
def run(self):
# Create a new Faker and tell it how to create User objects
faker = Faker(
cls=User,
init={
"id_num": generator.Sequence(),
"name": generator.Name(),
"age": generator.Integer(start=20, end=100)
}
)
# Create 5 users
for user in faker.create(5):
print("Adding user: %s" % user)
self.db.session.add(user)
And finally, you can call
$ flask seed run
to populate the database.

How can I create a table if not exist on Flask with SQLAlchemy?

I am using SQLAlchemy and I have the following code:
Model:
class User(db.Model):
__tablename__ = 'user'
__table_args__ = {'schema': 'task', 'useexisting': True}
id = Column(Integer, primary_key=True, autoincrement=True)
firstname = Column(String)
.env
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
app.py
def create_app(config_file):
"""Create a Flask application using the app factory pattern."""
app = Flask(__name__)
"""Load configuration."""
app.config.from_pyfile(config_file)
"""Init app extensions."""
from .extensions import db
db.init_app(app)
This creates the SQLite file if it does not exist, but not the tables of each model.
The question is what can I do in order to create the tables for each model?
Just add:
db.create_all()
in app.py at the end of create_app().
create_all() will create the tables only when they don't exist and would not change the tables created before.
If you want to create the database and the tables from the command line you can just type:
python
from app.py import db
db.create_all()
exit()
The working example:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.secret_key = "Secret key"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///my_database.sqlite3"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
class Data(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50))
email = db.Column(db.String(50))
phone = db.Column(db.String(50))
db.create_all()
# add a row
# comment out after the 1st run
table_row = Data(name="My Name", email="myemail#mail.com", phone="123456")
db.session.add(table_row)
db.session.commit()
print "A row was added to the table"
# read the data
row = Data.query.filter_by(name="My Name").first()
print "Found:", row.email, row.phone
if __name__ == "__main__":
app.run(debug=True)
This is for Python 2.7, to run with Python 3.x just change the the print statements to call the print() function.
NOTE:
When using automatic model class constructor the arguments passed to model class constructor must be keyword arguments or there will be an error. Otherwise you can override the __init__() inside Data() class like this:
def __init__(self, name, email, phone, **kwargs):
super(Data, self).__init__(**kwargs)
self.name = name
self.email = email
self.phone = phone
In that case you don't have to use keyword arguments.
you need first to use Shell Context Processor to load automatically all Model objects
in app.py add
# import all models from all blueprints you have
from .users.models import User
#app.shell_context_processor
def make_shell_context():
return { 'db': db, 'User': User .. }
and then use Flask shell command
(venv) $ flask shell
>>> db
<SQLAlchemy engine=sqlite:///data-dev.sqlite> # something similar to that
>>>
>>> User
<class 'api.users.models.User'>
>>>
>>> # to create database if not exists and all tables, run the command below
>>> db.create_all()
maybe you'll need Flask-Migrate for advanced operations (migrations) on your database: create new table, update tables / fields ...

SqlAlchemy ForeignKey on different schema not found

So have 2 different models on 2 different schemas. One has a foreign key relation to the other. I run BaseOne.metadata.create_all(engine) then BaseTwo.metadata.create_all(engine) I get sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column...
BaseOne = declarative_base(metadata=MetaData(schema="a"))
BaseTwo = declarative_base(metadata=MetaData(schema="b"))
class Parent(BaseOne):
__tablename__ = "parent"
parent_id = Column(Integer, primary_key=True)
other_col = Column(String(20))
children = relationship("Child", backref="parent")
class Child(BaseTwo):
__tablename__ = "child"
child_id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("a.parent.parent_id"), nullable=False)
# Where I'm creating them
BaseOne.metadata.create_all(engine)
BaseTwo.metadata.create_all(engine)
Should note I've also tried explicitly stating the schema via __table_args__. Also I have connected to my postgres instance and have verified that the parent table exists with the target column.
It appears the issue was due to the fact I used multiple MetaData objects. It appears that they were unable to see each other. Simplified to a single declarative base and using __table_args__ to declare the schemas appeared to work. If someone knows how to declare multiple metadata objects and still be able to use .create_all feel free to post.
This may be solved by using Alembic to manage table creation. Ensure that all bases are included in the target_metadata list e.g.:
# pylint: skip-file
import os
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config
from sqlalchemy import pool
import unimatrix.ext.octet.orm
import gpo.infra.orm
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = [
unimatrix.ext.octet.orm.metadata,
gpo.infra.orm.Relation.metadata
]
# Configure SQLAlchemy to use the DB_URI environment variable.
config.set_main_option("sqlalchemy.url", os.environ["DB_URI"])
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
``

Flask-Sqlalchemy setup engine configuration

SqlAlchemy extension:
https://pythonhosted.org/Flask-SQLAlchemy/index.html
and i want to setup the engine with customer configuration using the parameters here:
http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html
I'm using the way described on Flask-SqlAlchemy documentation:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
EDIT:
How can I pass the following engine parameters in this kind of configuration:
isolation_level = 'AUTOCOMMIT', encoding='latin1', echo=True
method: SQLAlchemy() doesn't take these as arguments.
it's an open issue: https://github.com/mitsuhiko/flask-sqlalchemy/issues/166
you can try this
class SQLiteAlchemy(SQLAlchemy):
def apply_driver_hacks(self, app, info, options):
options.update({
'isolation_level': 'AUTOCOMMIT',
'encoding': 'latin1',
'echo': True
})
super(SQLiteAlchemy, self).apply_driver_hacks(app, info, options)
db = SQLiteAlchemy(app)
it’s just a config option. Here’s ours:
SQLALCHEMY_ENGINE_OPTIONS = {
"pool_pre_ping": True,
"pool_recycle": 300,
}
I set {'pool_pre_ping':True} like above!TypeError: Invalid argument(s) 'pool_pre_ping' sent to create_engine(), using configuration MySQLDialect_pymysql/QueuePool/Engine.
Please check that the keyword arguments are appropriate for this combination of components.
you can define different engine options for different binds overwriting the apply_driver_hacks and define the options for each of your databases. Forexample, if you want to define different pool classes for different databases:
app.config['SQLALCHEMY_DATABASE_URI'] = "monetdb://..//.."
app.config['SQLALCHEMY_BINDS '] = {
'pg': 'postgres+psycopg2://..//..'
}
app.config['POOL_CLASS'] = {'monetdb' : StaticPool , "postgres+psycopg2" : QueuePool}
class MySQLAlchemy(SQLAlchemy):
def apply_driver_hacks(self, app, info, options):
super().apply_driver_hacks(app, info, options)
try:
options['poolclass'] = app.config['POOL_CLASS'][info.drivername]
except KeyError: #if the pool class is not defined it will be ignored, means that it will use the default option
pass
db = MySQLAlchemy(app)

Categories