Flask-Migrate is just detecting the unique constraints not the whole model - python

When I try to use flask-migrate with my model and a Postgres database, it does not work. Even if the database is created at startup. Every time migrate only detects the following:
INFO [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['Auth_ID']'
INFO [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['ClosedCourse_ID']'
My Model (much larger but is poorly designed like this)
class SKAuthentication(db.Model):
__tablename__ = 'sk-authentication'
Auth_ID = db.Column(UUID(as_uuid=True), primary_key=True, unique=True)
Basic_Auth = db.Column(Text, nullable=False)
Basic_User = db.Column(Text, nullable=False)
Teacher_ID = db.Column(UUID(as_uuid=True), db.ForeignKey('sk-teacher.Teacher_ID'), nullable=False)
Model = db.Column(Text, nullable=True)
Phone_ID = db.Column(Text, nullable=True)
Brand = db.Column(Text, nullable=True)
VersionInstalled = db.Column(Text, nullable=False, default='0.0.0')
def __init__(self, teacher_id):
chars = string.digits + string.ascii_letters + string.punctuation
self.Basic_Password = ''.join(secrets.choice(chars) for _ in range(256))
self.Auth_ID = create_ID(teacher_id, token_1, auth_crypt)
self.Basic_Auth = sha512_crypt.encrypt(self.Basic_Password)
self.Basic_User = create_ID(self.Basic_Auth, self.Auth_Secret)
self.Teacher_ID = teacher_id
class SKDayilyClosedCourse(db.Model):
__tablename__ = 'sk-daily-closed-course'
ClosedCourse_ID = db.Column(UUID(as_uuid=True), primary_key=True, unique=True)
Teachers_Group_ID = db.Column(UUID(as_uuid=True), db.ForeignKey('sk-teachers-groups.Row_ID'), nullable=False)
Course_Date = db.Column(Date, nullable=False)
Closed = db.Column(BOOLEAN, nullable=False, default=False)
Reminded = db.Column(BOOLEAN, nullable=False, default=False)
def __init__(self, teachers_group_id, course_date, reminded):
self.ClosedCourse_ID = create_ID(teachers_group_id, False, False, course_date)
self.Teachers_Group_ID = teachers_group_id
self.Course_Date = course_date
self.Reminded = reminded
My run.py looks like this:
from flask import Flask, session
from flask_sqlalchemy import SQLAlchemy
from modules.extensions import csrf, migr, cors
from config import DevelopmentConfig
from flask_migrate import upgrade, migrate
db = SQLAlchemy()
migr = Migrate()
def create_app():
app = Flask(__name__)
csrf.init_app(app)
app.config.from_object(DevelopmentConfig)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
cors.init_app(app, resources={r"/mobile/v1/*": {"origins": "GET, POST, OPTIONS"}})
migr.init_app(app, db, render_as_batch=False)
with app.app_context():
import modules.database.model
db.create_all()
db.session.commit()
migrate()
upgrade()
from application import application
from application import application_ticket
from application import application_mobile
from application import application_bookings
app.register_blueprint(application.application)
app.register_blueprint(application_ticket.ticket, url_prefix="/ticket")
app.register_blueprint(application_mobile.mobile, url_prefix="/mobile/v1")
app.register_blueprint(application_bookings.bookings, url_prefix="/bookings/v1")
#app.before_request
def before_request():
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=1)
return app
This is the important part form my extensions.py
from flask_wtf import CSRFProtect
from flask_migrate import Migrate
from flask_cors import CORS
csrf = CSRFProtect()
migr = Migrate()
cors = CORS()
...
And that's my wsgi.py:
from run import create_app
app = create_app()
Once it worked, but I don't know how. I didn't change anything, so I tried to reproduce it and I encountered the same problem I mentioned before. I am not sure how to proceed with this problem.
I tried to create the tables inside the models.py and try to migrate everything from there, but still just the unique constraints.
Another way, I figured out was to import the models, which I also needed to create the tables with Flask-SQLAlchemy, but that changed nothing.
Next solution I've found was, to run everything from console. Nothing changed.
Other solution: Drop Table "alembic_version" -> didn't changed anything.
Hopefully someone can help me soon!
I am expecting to get following output:
INFO [alembic.autogenerate.compare] Detected added table '[<Tablename>]'
...
Thank you all!

Related

How do I correct a circular import with Flask and SQLAlchemy?

I'm working on a flask application that uses SQLAlchemy, and I have the models for the database declared in a different file 'db_schema.py' from the main application file 'app.py'. I am getting a circular import error when trying to import the models from db_schema.py back in to app.py.
Error:
ImportError: cannot import name 'User' from partially initialized module 'db_schema' (most likely due to a circular import) (/home/deric/projects/python/flask/chatroom/chatroom/project/db_schema.py)
db_schema.py
from app import db
from flask_login import UserMixin
from datetime import datetime
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(25), unique = True, index = True, nullable = False)
password = db.Column(db.String(125), unique = False, index = False, nullable = False)
email = db.Column(db.String(30), unique = True, index = True, nullable = False)
chatroom_id = db.Column(db.Integer, db.ForeignKey('chatroom.id'))
messages_sent = db.relationship('Message', backref='sender', lazy='dynamic', cascade='all, delete')
class Chatroom(db.Model):
id = db.Column(db.Integer, primary_key = True)
class Message(db.Model):
id = db.Column(db.Integer, primary_key = True)
chatroom_id = db.Column(db.Integer, db.ForeignKey('chatroom.id'))
sender_id = db.Column(db.Integer, db.ForeignKey('user.id'))
content = db.Column(db.String(150), unique = False, index = True)
time_sent = db.Column(db.DateTime, unique = False, index = True, default=datetime.utcnow)
app.py
from flask import Flask, render_template, url_for, redirect, request
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from forms import Registration_Form
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite3.db'
login_manager = LoginManager()
db = SQLAlchemy(app)
from db_schema import User, Message, Chatroom
#login_manager.user_loader
def load_user(user_id):
return User.query.filter(id=user_id).first()
#app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
One solution is to move the conflicted depedency variable app out to a third file. Import app from both db_schema.py and app.py.
Here's the dependency graph:
config.py (app) <---- db_schema.py (User, ...)
^ ^
| |
+------- app.py -------+
In code:
config.py:
from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite3.db'
db_schema.py:
from config import app
db = SQLAlchemy(app)
# model classes: User, ...
app.py:
from config import app
from db_schema import Chatroom, Message, User
login_manager = LoginManager()
#login_manager.user_loader
def load_user(user_id):
return User.query.filter(id=user_id).first()
#app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
If db seems misplaced in the db_schema.py file, you could move it out to config.py or its own file. You can rename config.py if you don't feel it represents what it does well.
Another way to resolve the dependency without more files is to provide a class or function that can be invoked to initialize a variable. This allows all files to be loaded before fulfilling the topological ordering requirements through function calls.
The canonical thread appears to be Flask circular dependency which has an answer showing the function-based approach.
Also: if you use plain SQLAlchemy instead of Flask-SQLAlchemy, then there's no SQLAlchemy(app) call. This has other benefits of loose coupling such as better model portability, with the drawback that initial configuration of session scoping is a bit more tedious and error-prone.

Best way to check if database already exists with flask-sqlalchemy, otherwise create

I have a database app, and want to create the tables and database on the fly when starting the app for the first time. If the app is restarted, it should only create the database and tables if they do not exist, otherwise ignore this creation. I have a few questions.
1.) Is this a proper way of dealing with dataset creation in Flask/SQL?
2.) If there are other ways, how to prevent Flask from overwriting existing .db file?
3.) Is the approach written below a valid approach?
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, render_template, request, redirect, g
import sqlite3
import os.path
import os
#from forms import RegistrationForm, LoginForm
#from flaskblog import db
#Get working directory
file_path = os.path.abspath(os.getcwd()+"/DATA/site.db")
app = Flask(__name__)
app.config["SECRET_KEY"] = "someweirdsecretkeywhatevermaybeuuid4"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///DATA/site.db"
db = SQLAlchemy(app)
#Define tables we need to have in databases
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), unique=False, default="default.jpg")
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
if not os.path.exists(("/"+str(db.engine.url).strip("sqlite:////"))):
db.create_all()
else:
print("database already exists")
You don't have to import os and check whether the DB file is there or not.
Call db.create_all() method inside here:-
if __name__ == "__main__":
db.create_all()
Do something here
It will create sqlite3 DB once. If you are using another DB, this will create the tables once.

Flask_User and Blueprint - how to structure the app?

I am creating my first larger Python Flask app and one of the modules I will be using is Flask_User. To make all a bit more manageable I wanted to impost a blueprint and factory app.
The structure is simple:
app.py
config.py
auth/
__init__.py
models.py
So the goal is to have all authentication stuff in auth section. However, it comes down to where to initialize UserManager not to have errors, circular references, etc.
user_manager = UserManager(app, db, User)
How code looks like - app.py
from flask import Flask
# Configuration
from config import Config, HomeDevelopmentConfig
# Extensions
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# INITIATE DB
db = SQLAlchemy()
migrate = Migrate() # this will run SQLAlchemy as well
from flask_user import UserManager
from auth.models import User
user_manager = UserManager()
# APPLICATION FACTORY
def create_app(config_class=Config):
# Create app object
app = Flask(__name__)
# Load configuration
app.config.from_object(config_class)
# Register extensions
db.init_app(app)
migrate.init_app(app, db)
user_manager = UserManager(app, db, User) # !!! this will not work - circular reference to User as User needs db
# Close session with DB at the app shut down
#app.teardown_request
def shutdown_session(exception=None):
db.session.remove()
with app.app_context():
# Register blueprints
# Main flask app
from main import bp_main
app.register_blueprint(bp_main)
# Authentication (flask-user)
from auth import bp_auth
app.register_blueprint(bp_auth)
return app
# RUN APPLICATION
if __name__ == '__main__':
app = create_app(HomeDevelopmentConfig)
app.run()
models.py (should contain data structure for authentication)
from app import db
from flask_user import UserMixin #, UserManager
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, server_default='')
first_name = db.Column(db.String(100), nullable=False, server_default='')
last_name = db.Column(db.String(100), nullable=False, server_default='')
email_confirmed_at = db.Column(db.DateTime())
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='0')
# Define the relationship to Role via UserRoles
roles = db.relationship('Role', secondary='user_roles')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
class UserRoles(db.Model, UserMixin):
__tablename__ = 'user_roles'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))
and finally init.py for authentication:
from flask import blueprints
bp_auth = blueprints.Blueprint('auth', __name__)
from auth import models
Looks like mission impossible, but maybe you have some good hints. What works is to put all the code into app.py, but that make

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()

Using proper file structure with SQLAlchemy and how to add data to db

I am trying to build a simple blogging platform to learn Python and Flask. I am using SQLAlchemy to connect to a Postgres db hosted on Heroku and flask_s3 to serve static files from an AWS bucket. Im mostly following along from this:
https://gist.github.com/mayukh18/2223bc8fc152631205abd7cbf1efdd41/
All was going well, it is correctly hosted on Heroku and connceted to AWS S3 bucket, ready to go. I am stuck however on how to add blog posts to the database through some kind of form or route that will allow me to fill in the attributes of a blog post (found in Post in models.py below)
I have html templates that get rendered and the following three files, app.py, manage.py and models.py.
app.py:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_s3 import FlaskS3
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = '*URI for DB hosted on heroku'
app.config['FLASKS3_BUCKET_NAME'] = 'my S3 bucket on AWS'
db = SQLAlchemy(app)
s3 = FlaskS3(app)
from models import Post
#routes to templates to be rendered
if __name__ == '__main__'
app.run(debug=True)
manage.py:
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app, db
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
and models.py:
from manage import db,app
class Post(db.Model):
__tablename__ = 'blogposts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=True)
content = db.Column(db.Text, index=True, unique=True)
date = db.Column(db.DateTime, index=True, unique=True)
tag = db.Column(db.String(120), index=True, unique=True)
cover = db.Column(db.String(120), index=True, unique=True)
def __repr__(self):
return '<Post: %r>' % (self.title)
my file strucure is:
-blog
--__pycache__
--migrations
--static
--templates
app.py
manage.py
models.py
Pipfile
Pipfile.lock
Procfile
I want to work on this locally (before I publish anything to Heroku) but have no idea what to do from here. Anyone have advice on how to go about creating a route to add blog posts and saving them to a local Postgres instance before I go about pushing finalized blog posts to Heroku?
the gist I have been following has something like this as a route:
#app.route('/add/')
def webhook():
#post attributes defined
p = Post(id = id, title = title, date = datetime.datetime.utcnow, content = content, tag = tag, cover = cover)
print("post created", p)
db.session.add(p)
db.session.commit()
return "post created"
when I try and run it locally I get the following error so I'm not sure I have the files connecting properly.
File "/Users/Mb/Desktop/datadude/app.py", line 15, in <module>
from models import Post
ImportError: cannot import name 'Post'
Sorry if this is the wrong place for this. If there is a better place to ask for advice let me know.
The problem exists in circular dependencies.
You could move initializing of SQLAlchemy to models.py. Then only run method init_app on db object in app.py.
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Post(db.Model):
__tablename__ = 'blogposts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=True)
content = db.Column(db.Text, index=True, unique=True)
date = db.Column(db.DateTime, index=True, unique=True)
tag = db.Column(db.String(120), index=True, unique=True)
cover = db.Column(db.String(120), index=True, unique=True)
def __repr__(self):
return '<Post: %r>' % (self.title)
app.py
import datetime
from flask import Flask, render_template
from flask_s3 import FlaskS3
from models import db
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = '*URI for DB hosted on heroku'
app.config['FLASKS3_BUCKET_NAME'] = 'my S3 bucket on AWS'
db.init_app(app)
s3 = FlaskS3(app)
from models import Post
#app.route('/add/')
def webhook():
#post attributes defined
p = Post(id = id, title = title, date = datetime.datetime.utcnow, content = content, tag = tag, cover = cover)
print("post created", p)
db.session.add(p)
db.session.commit()
return "post created"
#routes to templates to be rendered
if __name__ == '__main__':
app.run(debug=True)
You can read more about it https://github.com/slezica/bleg/blob/master/data/posts/2014-03-08-avoiding-circular-dependencies-in-flask.md

Categories

Resources