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.
Related
I need help with Enum field type as it is not accepted by Swagger and I am getting error message **TypeError: Object or Type eGameLevel is not JSON serializable**. Below is the complete set of code for table. Complete set of code with DB table and sqlalchemy settings is provided. I already tried it with Marshmallow-Enum Flask package and it didn't worked. Looking for kind help with some explanation about the solution so I can learn it well. :-)
I am using MySQL with the Flask. In Postgres its pretty easy to manage all the choice fields. All I need is a working example or a link to repository where MySQL choice fields are showing up in swagger drop down.
My Model:
import enum
from app import db
from typing import List
class eGameLevel(enum.Enum):
BEGINNER = 'Beginner'
ADVANCED = 'Advanced'
class Game(Base):
__tablename__ = 'game_stage'
id = db.Column(db.Integer(), primary_key=True)
game_level= db.Column(db.Enum(eGameLevel),
default=eGameLevel.BEGINNER, nullable=False)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
user = db.relationship('User', backref='game__level_submissions', lazy=True)
def __init__(self, game_level, user_id):
self.game_level = game_level
self.user_id = user_id
def __repr__(self):
return 'Game(game_level%s, ' \
'user_id%s'%(self.game_level,
self.user_id)
def json(self):
return {'game_level':self.game_level,
'user_id':self.user_id}
#classmethod
def by_game_id(cls, _id):
return cls.query.filter_by(id=_id)
#classmethod
def find_by_game_level(cls, game_level):
return cls.query.filter_by(game_level=game_level)
#classmethod
def by_user_id(cls, _user_id):
return cls.query.filter_by(user_id=_user_id)
#classmethod
def find_all(cls) -> List["Game"]:
return cls.query.all()
def save_to_db(self) -> None:
db.session.add(self)
db.session.commit()
def delete_from_db(self) -> None:
db.session.delete(self)
db.session.commit()
My Schema
from app import ma
from app.models import Gode
class GameSchema(ma.SQLAlchemyAutoSchema):
game = ma.Nested('GameSchema', many=True)
class Meta:
model = Game
load_instance = True
include_fk= True
My Resources:
from flask_restx import Resource, fields, Namespace
from app.models import Game
from app import db
from app.schemas import GameSchema
GAME_REQUEST_NOT_FOUND = "Game request not found."
GAME_REQUEST_ALREADY_EXSISTS = "Game request '{}' Already exists."
game_ns = Namespace('Game', description='Available Game Requests')
games_ns = Namespace('Game Requests', description='All Games Requests')
game_schema = GameSchema()
games_list_schema = GameSchema(many=True)
gamerequest = game_ns.model('Game', {
'game_level': fields.String('Game Level: Must be one of: BEGINNER, ADVANCED.'),
'user_id': fields.Integer,
})
class GameRequestsListAPI(Resource):
#games_ns.doc('Get all Game requests.')
def get(self):
return games_list_schema.dump(Game.find_all()), 200
#games_ns.expect(gamerequest)
#games_ns.doc("Create a Game request.")
def post(self):
game_json = request.get_json()
game_data = game_schema.load(game_json)
game_data.save_to_db()
return game_schema.dump(game_data), 201
Instead of trying to manage Enum fields for MySQL schema I suggest to use another table with backref to your eGameLevel. You can get rid of this whole fuss and also in future if you needed to add another option in your choice field you won't have to hardcode it.
Simply create a main table as Game and sub table as eGameLevel (with only one string field). You will be able to access choices from your Game table.
Whenever I get stuck I go to basics as mentioned in here.
I made a small example just to test the serialization of an Enum
from enum import Enum
import sqlalchemy as sa
from flask import Flask
from flask_restx import Api, Namespace, Resource
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
class eGameLevel(str, Enum):
BEGINNER = "Beginner"
ADVANCED = "Advanced"
engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class Game(Base):
__tablename__ = "game"
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
level = sa.Column(sa.Enum(eGameLevel), default=eGameLevel.BEGINNER, nullable=False)
def __repr__(self):
return f"Game(id={self.id}, level={self.level})"
def json(self):
data = {"id": self.id, "level": self.level}
return data
Base.metadata.create_all(engine)
g1 = Game(level=eGameLevel.BEGINNER)
g2 = Game(level=eGameLevel.ADVANCED)
session.add_all([g1, g2])
session.commit()
query_content = session.query(Game).all()
games_ns = Namespace("Game Requests", description="All Games Requests")
app = Flask(__name__)
api = Api(app)
#api.route("/game")
class GameRequestsListAPI(Resource):
#games_ns.doc("Get all Game requests.")
def get(self):
data = [x.json() for x in query_content]
return data, 200
app.run(debug=True)
This example works and I think the serialization is possible due to the str in the Enum declaration: class eGameLevel(str, Enum).
Instead of using Enum:
class eGameLevel(enum.Enum):
BEGINNER = 'Beginner'
ADVANCED = 'Advanced'
You can make use of dictionary:
eGameLevel = {"BEGINNER": 1, "ADVANCED": 2}
Then you can replace enum type for sql data model to String type as:
game_level= db.Column(db.Integer(),
default=eGameLevel["BEGINNER"], nullable=False)
And make appropriate checks using the defined dictionary throughout application. This will also solve issues with alembic as well for making db migrations.
You would also require modifying some of your python files. I would rather do it here directly, and then you can look up to modify them:
#Import at Resources
from flask import request
from app.models import Game, eGameLevel
Post Part:
# For post part
payload = request.json
game_obj = Game(game_level=eGameLevel[payload["game_level"]], user_id=payload["user_id"])
db.session.add(game_obj)
db.session.commit()
Furthermore, I did not understand what the from app.models import Gode meant.
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 ...
I have an flask app, using flask-slqalchemy to query a mysql database
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:password#localhost/abc'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
there is a table "users" in "abc" database and it is already populated with several hundred rows.
Now i need to import this existing table, rather than first defining it with db.Model
how do i call the table?
if i do this
from sqlalchemy import Table
USERS = Table('users', db.metadata,autoload=True, autoload_with=db.engine)
then i am not able to make a query like
USERS.query.filter_by(done=1).with_entities(USERS.name,USERS.country).paginate(page, 15, False)
it generates an error
AttributeError: 'Table' object has no attribute 'query'
because this is sqlchemy command, not flask-sqlchemy, i dont fully understand this.
I have to first define the table USERS like i am creating it for the first time :
class USERS(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(500))
country = db.Column(db.VARCHAR(50))
def __init__(self, id, name, country):
self.id = id
self.name = name
self.country = country
def __repr__(self):
return self.id
only then i am able to use USERS to query the database through flask-sqlalchemy
How do i access the an existing table users using flask-sqlchemy in an flask app?
In sqlalchemy you should query table(s) with session if you want to query Table(). Because 'Table' object has no attribute 'query'. And you do not need to create table if it has existed, just use it. sqlalchemy existing database query
from sqlalchemy import Table, Column, String, create_engine, MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine()
metadata = MetaData()
test_ = Table('test', metadata,
Column('msg', String, primary_key=True),
Column('msg_', String)
)
Session = sessionmaker(bind=engine)
session = Session()
print(session.query(test_).filter_by(msg_ = "test").with_entities("msg","msg_").one())
# ('t', 'test')
In flask_sqlalchemy, it almost same as sqlalchemy did.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = ""
db = SQLAlchemy(app)
class test(db.Model):
msg = db.Column(db.String, primary_key=True)
msg_ = db.Column(db.String)
def __init__(self, msg, msg_):
self.msg = msg
self.msg_ = msg_
def __repr__(self):
return "msg: {} msg_: {}".format(self.msg,self.msg_)
result = test.query.filter_by(msg_="test").one()
print(result)
print(result.msg,result.msg_)
'''
msg: t msg_: test
t test
'''
I have the following code that sets up my database with Flask-SQLAlchemy. I'm getting an exception "AttributeError: scoped_session object has no attribute 'create_all'". Can someone please explain to me why I'm getting the error and how I can fix it? :)
__init__.py:
from flask import Flask
app = Flask(__name__)
from app.db import DB
database = DB()
database.create_all()
db/__init__.py:
from flask import session
from flask.ext.sqlalchemy import SQLAlchemy
from app import app
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://user:pass#dburl:/schema'
connection = SQLAlchemy(app)
from app.db.models import *
class DB():
def __init__(self):
pass
def create_all(self):
connection.session.create_all()
connection.session.commit()
print("done")
models.py:
from app.db import connection as db
class Test(db.Model):
__tablename__ = 'Test'
id = db.Column("ID", db.Integer, primary_key=True)
name = db.Column("Name", db.String(100), nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Test: %s>' % self.name
Any guidance would be greatly appreciated!
It's connection.create_all(), you added session in the middle.
Unrelated to the immediate problem, there are other things that don't look right.
You don't need to commit after running create_all.
The extension instance is usually named db. The DB class does nothing, just write your create_all function on its own.
You don't need to specify table or column names for Flask-SQLAlchemy models in most cases, and upper case names are not generally used.
Don't use tabs, PEP 8 recommends 4 spaces.
We use a central class model for a wide variety of python modules. This model is defined using SQLAlchemy. The classes all inherit from declarative_base.
For example, our model definitions look something like this:
Base = declarative_base()
class Post(Base):
__tablename__ = 'Posts'
id = Column(INT, primary_key=True, autoincrement=True)
body = Column(TEXT)
timestamp = Column(TIMESTAMP)
user_id = Column(INT, ForeignKey('Users.uid'))
We have been building a flask web-application in which we employ this same model. We have discovered a tricky problem in that flask-sqlalchemy appears to be designed in such a way that it expects all classes used in its model to have been defined by passing in an active instance of the session. Here is an example of a "proper" flask-sqalchemy class model definition:
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)
Note that the above example for flask-sqlalchemy requires an already-instantiated sql session. This has horrified us, because we are completely at a loss as to how to integrate our SqlAlchemy model into flask. We really want to use the flask-security suite in particular.
This problem has been brought up before on SO. Here, for example:
How to use flask-sqlalchemy with existing sqlalchemy model?
Our requirements are different from those of whoever accepted the response there. The response points out that one loses the ability to use User.query, but this is precisely one of the things we must retain.
It is not feasible to abandon our nice, elegant, central class model definition in favor of what flask-sqlalchemy appears to require. Is there any way for us to associate our model with the SQLAlchemy() object? Bonus points for getting us the .query() method on our classes which appears to be required by flask-security.
Solution:
As of today, the best way to do this is as follows:
Implement or import sqlalchemy base
from sqlalchemy.ext.declarative import declarative_base
base = declarative_base()
class Base(base):
__abstract__ = True
uid = Column(Integer, primary_key=True, autoincrement=True)
Register the external base:
from flask_sqlalchemy import SQLAlchemy
from model.base import Base
app = Flask(__name__)
db = SQLAlchemy(app, model_class=Base)
Archived for posterity:
I spent a lot of time looking for an answer to this. This is a lot easier to do today than it was when I originally asked the question, but it still isn't exactly simple.
For anyone who decides to do security themselves, I recommend the following excellent exposition of common design patterns which use flask, but which avoid employing unnecessary dependencies like flask-security:
https://exploreflask.com/users.html
UPDATE:
For anyone interested, a patch has been in the works for some time related to this. As of now it still isn't released, but you can check its progress here:
https://github.com/mitsuhiko/flask-sqlalchemy/pull/250#issuecomment-77504337
UPDATE:
I have taken the code from the above mentioned patch and created a local override for the SQLAlchemy object which allows one to register an external base. I think this is the best option available until such time as FSA gets around to adding this officially. Here is the code from that class for anyone interested. Tested working with Flask-SqlAlchemy 2.2
Patching in register_external_base:
import flask_sqlalchemy
'''Created by Isaac Martin 2017. Licensed insofar as it can be according to the standard terms of the MIT license: https://en.wikipedia.org/wiki/MIT_License. The author accepts no liability for consequences resulting from the use of this software. '''
class SQLAlchemy(flask_sqlalchemy.SQLAlchemy):
def __init__(self, app=None, use_native_unicode=True, session_options=None,
metadata=None, query_class=flask_sqlalchemy.BaseQuery, model_class=flask_sqlalchemy.Model):
self.use_native_unicode = use_native_unicode
self.Query = query_class
self.session = self.create_scoped_session(session_options)
self.Model = self.make_declarative_base(model_class, metadata)
self._engine_lock = flask_sqlalchemy.Lock()
self.app = app
flask_sqlalchemy._include_sqlalchemy(self, query_class)
self.external_bases = []
if app is not None:
self.init_app(app)
def get_tables_for_bind(self, bind=None):
"""Returns a list of all tables relevant for a bind."""
result = []
for Base in self.bases:
for table in flask_sqlalchemy.itervalues(Base.metadata.tables):
if table.info.get('bind_key') == bind:
result.append(table)
return result
def get_binds(self, app=None):
"""Returns a dictionary with a table->engine mapping.
This is suitable for use of sessionmaker(binds=db.get_binds(app)).
"""
app = self.get_app(app)
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
retval = {}
for bind in binds:
engine = self.get_engine(app, bind)
tables = self.get_tables_for_bind(bind)
retval.update(dict((table, engine) for table in tables))
return retval
#property
def bases(self):
return [self.Model] + self.external_bases
def register_base(self, Base):
"""Register an external raw SQLAlchemy declarative base.
Allows usage of the base with our session management and
adds convenience query property using self.Query by default."""
self.external_bases.append(Base)
for c in Base._decl_class_registry.values():
if isinstance(c, type):
if not hasattr(c, 'query') and not hasattr(c, 'query_class'):
c.query_class = self.Query
if not hasattr(c, 'query'):
c.query = flask_sqlalchemy._QueryProperty(self)
# for name in dir(c):
# attr = getattr(c, name)
# if type(attr) == orm.attributes.InstrumentedAttribute:
# if hasattr(attr.prop, 'query_class'):
# attr.prop.query_class = self.Query
# if hasattr(c , 'rel_dynamic'):
# c.rel_dynamic.prop.query_class = self.Query
To be used like so:
app = Flask(__name__)
db = SQLAlchemy(app)
db.register_base(base)