Flask separate models into multiple files - python

I'm setting up a new Flask app, where I want to have all of my SQLAlchemy models in separate files. The overall structure should end up looking like this:
flask-app
|
|--api
| |--user.py
| |--example.py
|
|--controllers
| |--user.py
| |--example.py
|
|--models
| |--user.py
| |--example.py
|
|-main.py
|-modelref.py
|-config.py
In terms of code here's what i have so far:
main.py:
import os
from flask import Flask, send_from_directory
from flask_bcrypt import Bcrypt
from flask_cors import CORS
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import modelref
from apiref import load_api
app = Flask(__name__, static_url_path='', static_folder='./../react/public')
app.config.from_object(os.getenv("APP_SETTINGS", "config.Development"))
CORS(app)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
bcrypt = Bcrypt(app)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder,'index.html')
load_api(app)
The modelref import is a shorthand of sorts. In the modelref.py file I import every model that I have in the models folder like so:
modelref.py:
from models.user import User
from models.example import Example
Then the model itself is something along the lines of:
models/user.py:
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
first_name = db.Column(db.String(128), nullable=False)
last_name = db.Column(db.String(128), nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
def __init__(self, username, email, password, first_name, last_name):
self.username = username
self.email = email
self.password = bcrypt.generate_password_hash(password, flask.current_app.config.get('BCRYPT_LOG_ROUNDS')).decode()
self.first_name = first_name
self.last_name = last_name
self.registered_on = datetime.datetime.now()
def save(self):
db.session.add(self)
db.session.commit()
The model gets manipulated in a controller class defined here:
controllers/user.py:
class UserController:
#staticmethod
def register(username: str, password: str, email: str, first_name: str, last_name: str) -> dict:
result: dict = {}
try:
user = User(
username=username,
password=password,
email=email,
first_name=first_name,
last_name=last_name
)
user.save()
except IntegrityError:
User.rollback()
raise ResourceExists("user already exists")
return login(user.username, user.password);
#staticmethod
def login(username: str, password: str):
user = User.query.filter_by(username).first()
if user:
if bcrypt.check_password_hash(user.password, password):
token = user.encode_auth_token(user.id)
return {
"username": user.username,
"first_name": user.first_name,
"last_name": user.last_name,
"token": token
}
else:
abort(400, "Invalid Password")
else:
abort(404, "User Not Found")
The controller is accessed in an API resource defined using pluggable views. The views and routes are registered with the app in the load_app(app) method. I'm not sharing that whole pipeline, because I believe it is out of scope, but if anyone believes it's not, than I will add it into the post.
So now here's the problem. If I try to access the model in the code or run flask db migrate, I get an error that db is not defined.
A solution I've seen is to add a db=SQLAlchemy line in the model, but then the flask db migrate doesn't find this model, likely because the mentioned line creates a new SQLAlchemy instance, so it's essentially unaware of the models existence.
With this setup I'm unable to import the db object from main since that causes a circular import due to the model being imported in the controller.
Is there a way I can pass a reference to db to the model without an import, or make the model search for the "current" instance of 'db'. Or is there any way to reorganize the main code so that I can maintain the general structure, but be able to run migrations?

It turns out that in a case such as mine, the best option is to apply the application factory pattern. I now have an app.py module, where I initialize all the modules I need in a create_app method like so:
app.py:
import os
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
bcrypt = Bcrypt()
def create_app():
app = Flask(__name__, static_url_path='', static_folder='./../react/public')
app.config.from_object(os.getenv("APP_SETTINGS", "config.Development"))
import modelref
db.init_app(app)
migrate.init_app(app, db)
bcrypt.init_app(app)
return app
And them my main.py file simply invokes the create_app method and then sets up routes and API resources like so:
main.py:
from flask import send_from_directory
from flask_cors import CORS
from apiref import load_api
from app import create_app
app = create_app()
CORS(app)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder,'index.html')
load_api(app)
Running import modelref within the create_model method makes all the models imported in modelref visible to the app and the flask db commands.

Related

Marshmallow returning empty JSON after dumping

I am trying to implement a restful API with Flask using flask-restful, flask-sqlalchemy and flask-marshmallow. I am aware there are many questions about this issue but I still can't figure it out.
Solutions like using strict=True in marshmallow schemas or adding .data to user_schema.dump(result) don't let me see my result in the response body.
Here is my code :
# User.py file
from flask import jsonify, request
from flask_restful import Resource
from src import db
from src.models.user import User
from src.validation import UserSchema
class Users(Resource):
def get(self):
try:
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
user_schema = UserSchema()
result = user_schema.dump(user)
return jsonify(result)
except:
return jsonify({'error_message': 'User not found'})
def post(self):
try:
data = request.get_json()
user = User(data["firstName"], data["lastName"],
data["email"], data["password"])
db.session.add(user)
db.session.commit()
user_schema = UserSchema()
result = user_schema.dump(user)
return result
except:
return jsonify({'error_message': 'User already exists in database'})
The init.py file :
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["SQLALCHEMY_ECHO"] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql:///gauss"
ma = Marshmallow(app)
api = Api(app)
db = SQLAlchemy(app)
from src import routes
The validation.py file :
from src import ma
from src.models.user import User
class UserSchema(ma.SQLAlchemySchema):
class Meta:
model = User
And the User.py schema for the database :
from src import db
from datetime import datetime
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
firstName = db.Column(db.String(20))
lastName = db.Column(db.String(20))
email = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(), nullable=False)
createdAt = db.Column(db.DateTime, default=datetime.utcnow)
def __init__(self, firstName, lastName, email, password):
self.firstName = firstName
self.lastName = lastName
self.email = email
self.password = password
I've been looking for solutions for a few days now and it seems that marshmallow returning empty json occures mostly when querying with the .all() method for instance, because marshmallow .dump() method misses the Many=True configuration.
I'm probably missing something but I can't wrap my head around it. Any help appreciated !
i had the same problem, the solution to my problem was to change UserSchema(ma.SQLAlchemySchema): to UserSchema(ma.SQLAlchemyAutoSchema):

Schedule SQLAlchemy to clear all rows from a table

I'm trying to create a function that can be scheduled to delete all rows within an SQLAlchemy model.
I'm trying to use apscheduler to accomplish this task. But I keep getting an error that says:
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'flask_sqlalchemy.model.DefaultMeta' is
not mapped; was a class (app.models.User) supplied where an instance was required?
Am I missing something?
Here is my app/__init__.py:
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from config import Config
app = Flask(__name__)
db = SQLAlchemy()
login = LoginManager()
app.config.from_object(Config)
db.init_app(app)
login.init_app(app)
login.login_view = 'login'
from app import routes, models
and here is my manage.py:
from apscheduler.schedulers.background import BackgroundScheduler
from app import app, db
from flask_migrate import Migrate
from flask_script import Manager
from app.models import User
manager = Manager(app)
migrate = Migrate(app, db)
def clear_data():
db.session.delete(User)
print("Deleted User table!")
#manager.command
def run():
scheduler = BackgroundScheduler()
scheduler.add_job(clear_data, trigger='interval', seconds=5)
scheduler.start()
app.run(debug=True)
if __name__ == '__main__':
manager.run()
Also, here's my model:
from app import db, login
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
api_token = db.Column(db.String(50), unique=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<models.py {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
As your error indicated, it is expecting an instances of an object, and you instead passed it a class. I think the issue is the first line in the clear_data function:
db.session.delete(User)
It was expecting an instances of a User record to delete, and doesn't know how to delete the whole table using just the model.
Check out this answer on how to delete all rows in a table. There are a few ways to do this, but this may be the least change for you:
db.session.query(User).delete()
In this case you are adding the step of SELECTing all the records in the table User maps to, then deleting them.
P.S.: as mentioned in the linked answer, you need to .commit() your session, otherwise it won't stick, and will rollback after you close the connection.
db.session.commit()
Code snippet:
db.session.query(model_name).delete()
db.session.commit()

Flask - Migrate models from blueprints

I'm having trouble creating a modular application using Flask using blueprints. I'm not sure how to migrate models defined in blueprints.
My app looks as follows:
- app
- __init__.py
- user
__init__.py
models.py
app.__init__.py looks as follows:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config
from flask_migrate import Migrate
from app.auth import bp as user_bp
db = SQLAlchemy()
app = Flask(__name__)
app.config.from_object(Config)
app.register_blueprint(user_bp, url_prefix='/user')
migrate = Migrate(app, db)
from app.auth.models import User
user/.__init__.py:
from flask import Blueprint
bp = Blueprint('user', __name__)
user/models.py:
from app import db
class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(128))
last_name = db.Column(db.String(128))
user_name = db.Column(db.String(120))
password_hash = db.Column(db.String(220))
def __repr__(self):
return '<User {}>'.format(self.email)
When trying to run flask db migrate... no models are being detected and I think I have some sort of circular dependency.
Can someone please explain how to do this properly?
The User model (user/models.py) should be imported somewhere in the application, try to import into the blueprint file (user/__init__.py)
from flask import Blueprint
from app.user.models import User
bp = Blueprint('user', __name__)
app.py
from flask import Flask
from flask_restful import Resource, Api
from user import user_blueprint
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
app.register_blueprint(user_blueprint)
cors = CORS(app, resources={r"/*": {"origins": "*"}})
if __name__ == '__main__':
app.run(debug=True)
model/user.py
from sqlalchemy import Column, INTEGER, String
class User(Base):
__tablename__ = 'USER'
__table_args__ = {'schema': 'MAPPING'}
ID = Column(INTEGER(11), primary_key=True)
FIRST_NAME = Column(String(255), nullable=False, server_default=text("''"))
LAST_NAME = Column(String(255), nullable=False, server_default=text("''"))
EMAIL = Column(String(255), nullable=False, server_default=text("''"))
route/user_route
from flask import Blueprint, jsonify, request
from flask_restful import Resource, Api
user_blueprint = Blueprint('app/', __name__)
user_api = Api(user_blueprint, prefix='/')
class User(Resource):
def get(self):
db_conn = //db connection Object
results = // your query
db_conn.close()
return jsonify(results)
user_api.add_resource(User, '/user')

update state with flask sqlalchemy with postgres will not commit to database

I have read quite a bit of documentation and I can't see what is wrong with these lines
update_this = User.query.filter_by(email=email).first()
update_this.emailconfirmed = True
db.session.commit()
...and yet when I deploy the boolean column 'emailconfirmed' never is update to True. I have confirmed with print statements that update_this.emailconfirmed is False at the exact point in the code shown above... I just can't seem to update that value. Does anybody know what tests I can do, what imports I should check etc. etc.
Right now this is the top of my main .py file where the above code appears
from flask import Flask, render_template, request, session, redirect, url_for, make_response
# the following line imports from models.py
from models import db, User
# the following line imports SignupForm from forms.py
from forms import SignupForm, LoginForm
from flask_mail import Mail, Message
from itsdangerous import URLSafeTimedSerializer
# Production (causes Heroku to redirect to SSL)
from flask_sslify import SSLify
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
sslify = SSLify(app)
sslify = SSLify(app, subdomains=True)
app.config.from_pyfile('config_file.cfg')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
db = SQLAlchemy(app)
mail = Mail(app)
ts = URLSafeTimedSerializer(app.config['SECRET_KEY'], salt=app.config['SALT'])
and this is my models.py file
from flask_sqlalchemy import SQLAlchemy
from werkzeug import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(100))
lastname = db.Column(db.String(100))
role = db.Column(db.String(20))
roleapproved = db.Column(db.Boolean)
school = db.Column(db.String(100))
email = db.Column(db.String(120), unique=True)
emailconfirmed = db.Column(db.Boolean)
pwdhash = db.Column(db.String(100))
def __init__(self, firstname, lastname, role, school, email, password):
self.firstname = firstname.title()
self.lastname = lastname.title()
self.role = role.lower()
if role.lower() == 'student':
self.roleapproved = True
if role.lower() == 'teacher':
self.roleapproved = False
self.school = school.title()
self.email = email.lower()
self.set_password(password)
self.emailconfirmed = False
def set_password(self, password):
self.pwdhash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.pwdhash, password)
def __repr__(self):
return '<User {0}>'.format(self.email)
Any help on doing the update I mentioned above would be greatly appreciated!!
Ideally you want to maintain a single session throughout your application lifecycle. This way it makes it easy to reason about and you avoid binding sessions to individual models.
Thanks #Ilja Everila
In main.py instead of initializing SQLAlchemy
you should write,
db.init_app(app)
Define a save instance method for your User model.
def save(self):
"""Saves model object instance
"""
db.session.add(self)
db.session.commit()
You can call this method to save the instance as
update_this.save()
Another way to update the entity is to get the specific object session before committing
from sqlachemy.orm import session
...
session = session.object_session(update_this)
session.commit()

Flask python app.py doesn't run but manage.py runserver does [duplicate]

This question already has an answer here:
Tyring to set up modelview with Flask-Admin causes ImportError
(1 answer)
Closed 5 years ago.
I have a flask app which I am trying to run. It seems when I run python app.py, there is a circular import of some sort. Here is the code for both my models.py and app.py:
models.py
import datetime
from app import bcrypt, db
class User(BaseModel, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
admin = db.Column(db.Boolean, nullable=False, default=False)
def __init__(self, email, password, admin=False):
self.email = email
self.password = bcrypt.generate_password_hash(password)
self.registered_on = datetime.datetime.now()
self.admin = admin
app.py
from flask import Flask, render_template
from flask import request, jsonify, session
from flask.ext.bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from models import User
app = Flask(__name__)
db = SQLAlchemy()
POSTGRES = {
'user': 'postgres',
'db': 'postgres',
'host': 'localhost',
'port': '5432',
}
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s#%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SECRET_KEY'] = 'lol'
bcrypt = Bcrypt(app)
db.init_app(app)
#app.route('/api/register', methods=['POST'])
def register():
json_data = request.json
user = User(
email=json_data['email'],
password=json_data['password']
)
try:
db.session.add(user)
db.session.commit()
status = 'success'
except:
status = 'this user is already registered'
db.session.close()
return jsonify({'result': status})
manage.py
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app, db
from models import User
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
#manager.command
def create_admin():
"""Creates the admin user."""
db.session.add(User(email='admin#admin.com', password='admin', admin=True))
db.session.commit()
if __name__ == '__main__':
manager.run()
I have played around with the imports but nothing seems to work, I usually will get a circular import error like this:
Traceback (most recent call last):
File "app.py", line 7, in <module>
from models import User
File "/Users/Rishub/Desktop/apps/topten/models.py", line 3, in <module>
from app import bcrypt, db
File "/Users/Rishub/Desktop/apps/topten/app.py", line 7, in <module>
from models import User
ImportError: cannot import name User
Does anyone know the solution to this?
In addition, if I run python manage.py runserver, my app runs fine so I am curious as to why this works but python app.py does not.
in models.py you are importing:
from app import bcrypt, db
in app.py you are importing models.
from models import User
To fix it restructure the program as shown here: http://flask.pocoo.org/docs/0.12/patterns/sqlalchemy/
create a database.py file with database configs etc.
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
import models
Base.metadata.create_all(bind=engine)
use this in models.py as
from database import Base
from sqlalchemy import Column, Integer, String
# instead of db.column directly use Column
class User(Base):
and app files as:
from database import init_db
init_db()
from database import db_session
from models import User
#app.route('/api/register', methods=['POST'])
def register():
json_data = request.json
user = User(
email=json_data['email'],
password=json_data['password']
)
db_session.add(user)
db_session.commit()
return jsonify({'result': 'success'})

Categories

Resources