How to separate flask files when two files depend on each other? - python

I'm trying to develop a database driven flask app.
I have an api.py file which has the flask app, api and SQLAlchemy db objects and a users.py file which contains the routes ands code to create a database table.
In the users.py file, there's a UserManager Resource which has the routes. I have to add this resource to the API from this file.
So users.py needs to import the db and the api from the api.py file and api.py needs to serve the flask app so it has all the variables users.py needs, but api.py also needs to import users.py in order to use it as a flask blueprint.
api.py:
...
from user import user
app = Flask(__name__)
app.register_blueprint(user)
api = Api(app)
...
db = SQLAlchemy(app)
ma = Marshmallow(app)
...
if __name__ == '__main__':
app.run(debug=True)
users.py:
from api import api
user = Blueprint('user', __name__, template_folder='templates')
db = SQLAlchemy(api.app)
ma = Marshmallow(api.app)
class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
username = db.Column(db.String(32), unique=True, nullable=False)
password = db.Column(db.String(32))
first_name = db.Column(db.String(32))
last_name = db.Column(db.String(32))
...
...
class UserManager(Resource):
#user.route('/get/<user_id>', methods = ['GET'])
def get_user(user_id):
...
This of course results in errors due to circular imports. Question is, how do I separate the flask files with the routes from the api file when the blueprint has dependencies from the api (the db and the api objects)
I also somehow have to do something like api.add_resource(UserManager, '/api/users') but I'm not sure where that'd go given the circular import.
Tried reducing dependencies between two files, but couldn't achieve described goal without doing a 2 way import.
Either trying to get 2 way import to work or still have same structure of separate files with routes but using 1 way import.

This is a well known problem in flask. The solution is to use application factories.
Cookiecutter Flask does this really well and offers a good template. It is well worth to check out their repo and try to understand what they are doing.
Assuming you have a folder app and this folder contains a file __init__.py and your other files user.py, etc.
Create a file app/extensions.py with this content and any other extension you need to initialize.
...
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
...
Import the db object (which was created, but not initialized) in the files where you need it.
from app.extensions import db
In your app/api.py file
from flask import Flask
from app.extensions import db
from app.user import user as user_bp
def create_app():
app = Flask(__name__)
register_extensions(app)
register_blueprints(app)
return app
def register_extensions(app):
"""Register Flask extensions."""
db.init_app(app)
return None
def register_blueprints(app):
"""Register Flask blueprints."""
app.register_blueprint(user_bp)
return None
if __name__ == '__main__':
app = create_app()
app.run(debug=True)
Add stuff based on this approach as needed.

Related

How to create a SQLAlchemy database in one python file and use it in another python file of same project?

I am using flask_sqlalchemy to create a database in file1.py as:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
class DataBase(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
path = db.Column(db.String(200), nullable=False)
status = db.Column(db.String(100), nullable=False, default='Incomplete')
def __repr__(self):
return str(self.id)
I want to use this database in another python file which already hass app = Flask(__name__).
If the second file has an app, you will need to import the db engine from the current file
First of all, try not to use numbers in file names (and generally give better names)
So I recommend you call file1.py something like dal.py (dal for data access layer), and file2.py something like main.py or app.py.
This is how your folder should look like:
src -
| app.py
| dal.py
| __init__.py
Afterward, you have a few options
import the app from app.py
in dal.py you can do the following. This is not the best practice, but it will work
from app import app
...
def init_app():
db.init_app(app)
return app
init the app in app.py, I think this is the better idea.
In app.py, do the following
from flask import Flask
from dal import db
app = Flask(__name__)
db.init(app)
I highly recommend reading the docs and how to handle imports in python, I this this is a nice article

Flask objects on multiple sessions when they not should be

I have a similar problem to the user here: SQLAlchemy Object already attached to session
And I'm getting basically the same error:
'<Link at 0x7f31a785f630>' is already attached to session '1' (this is '15')
I'm really trying to figure out why multiple sessions are being created, when I only want one. I have two files __init__.py and models.py:
Lines of interest from __init__.py:
from .models import User, draft_new_link_message, load_history, load_messages, db
# Initialize app and such
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///my.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.secret_key = 'super secret keyssss'
socketio = SocketIO(app)
db.init_app(app)
app.app_context().push()
...
db.create_all()
From models.py:
db = SQLAlchemy()
class Link(db.Model):
__tablename__ = 'link'
id = db.Column(db.Integer, primary_key=True, nullable=False)
url = db.Column(db.String(500), nullable=False)
originator_id = db.Column(db.Integer, db.ForeignKey('user.id'))
originator = db.relationship("User", back_populates='history')
From these lines alone, it seems that I should be on one session. If I'm not, how do I format my code correctly to reduce headaches and make sure I don't have to transfer objects between sessions? Thanks!
Edit: Solution
The reason I structured my project this way was because a few pieces of documentation said this was the correct pattern (creating the db inside your models file and then callng db.init_app() to get it into the main file). But I guess this was a bad idea. I thought maybe I had to because I can't have both the files reference each other. But to get around this I wrote a method in the main file to get the db and called the import on the models function
My new __init__.py:
# Initialize app and such
app = Flask(name)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///browse_together.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.secret_key = 'super secret keysssss'
socketio = SocketIO(app)
db = SQLAlchemy(app)
# Provide a way for models.py (and any other files that needs it) to get access to the database
def get_db():
return db
# Now you can import models.py because it can use this database
from . import urltils, models
from .models import User, Group, get_groups, create_group, \
draft_new_link_message, load_history, load_messages, toggle_send
The new first few lines from models.py:
from flask_login import UserMixin
from . import urltils
from . import get_db
# Get an instance of the db from __init__
db = get_db()
I think this is more correct.
The reason I structured my project this way was because a few pieces of documentation said this was the correct pattern (creating the db inside your models file and then callng db.init_app() to get it into the main file). But I guess this was a bad idea. I thought maybe I had to because I can't have both the files reference each other. But to get around this I wrote a method in the main file to get the db and called the import on the models function
My new __init__.py:
# Other imports...
# Initialize app and such
app = Flask(name)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///my.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.secret_key = 'super secret keysssss'
db = SQLAlchemy(app)
# Provide a way for models.py (and any other files that needs it) to get access to the database
def get_db():
return db
# Now you can import models.py because it can use this database
from . import urltils, models
from .models import User, Group, get_groups, create_group, \
draft_new_link_message, load_history, load_messages, toggle_send
The new first few lines from models.py:
from flask_login import UserMixin
from . import urltils
from . import get_db
# Get an instance of the db from __init__
db = get_db()
I think this is more correct.

Initialize a Flask-WhooshAlchemy index when using the app factory pattern

I am developing an app with Flask using the application factory pattern. Initializing the Whoosh index doesn't work, because current_app cannot be used without setting up an app context explicitly. How do I do this?
__init__.py
from flask import Flask
from .frontend import frontend
def create_app(configfile=None):
app = Flask(__name__)
from .models import db
db.init_app(app)
app.register_blueprint(frontend)
return app
models.py
from flask_sqlalchemy import SQLAlchemy
import flask_whooshalchemy as wa
from flask import current_app
db = SQLAlchemy()
class Institution(db.Model):
__searchable__ = ['name', 'description']
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40))
description = db.Column(db.Text)
# this does not work
wa.whoosh_index(current_app, Institution)
You can't use current_app outside an application context. An application context exists during a request, or when explicitly created with app.app_context().
Import your models during create_app and index them with Whoosh there. Remember, the factory is where you're performing all setup for the application.
def create_app():
app = Flask('myapp')
...
from myapp.models import Institution
wa.whoosh_index(app, Institution)
...
return app
If you want to keep the code local to the blueprint, you can use the blueprint's record_once function to perform the index when the blueprint is registered on the app.
#bp.record_once
def record_once(state):
wa.whoosh_index(state.app, Institution)
This will be called at most once when registering the blueprint with an app. state contains the app, so you don't need the current_app.

Database created empty when models are in a separate file?

I am reading a book about flask development, and I'm pretty new to python generally.
File layout:
Project
|
|-- App
| |-- __init__.py
| |-- models.py
|
| main.py
Code inside __init__.py:
from flask import Flask
from flask.ext.bootstrap import Bootstrap
from flask.ext.sqlalchemy import SQLAlchemy
import os
from config import options
basedir = os.path.abspath(os.path.dirname(__file__))
bootstrap = Bootstrap()
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
'sqlite:///' + os.path.join(basedir, 'database_test.sqlite')
bootstrap.init_app(app)
db.init_app(app)
with app.app_context():
db.create_all()
print("DB created")
return(app)
I have been researching other people's issues on the site, mostly here where I found to use the with app.app_context(), and to instantiate the db without using app as part of the constructor.
Here is my models.py file:
from . import db
class User(db.Model):
__tablename__ = 'Users'
account_id = db.Column(db.Integer, primary_key=True)
personaname = db.Column(db.String(50), unique=False)
steamid = db.Column(db.String(64))
avatar = db.Column(db.String(200))
profileurl = db.Column(db.String(128))
is_personaname_real = db.Column(db.Integer)
def __repr__(self):
return '<User {0}>'.format(self.personaname)
I then rune the code from main.py which is just:
from app import create_app
app = create_app()
If I move the User class into the __init__.py function, everything is created fine. However, when the User class is inside it's own file, the database is created empty. I have tried using other ways of importing, maybe something like From app.__init__.py Import db, but that didn't work either.
Since defining your model directly in __init__.py works, it follows that you need to import your model(s) into __init__.py.
You can add an import in __init__.py after you create an SQLAlchemy object:
db = SQLAlchemy()
from .models import User
This works, but it feels horribly wrong and dirty to me. I don't know Flask that well, but this answer suggests that this is normal for Flask: https://stackoverflow.com/a/19008403/21945
This should work, seem you are missing the if statement
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()

Flask-SQLAlchemy import/context issue

I want to structure my Flask app something like:
./site.py
./apps/members/__init__.py
./apps/members/models.py
apps.members is a Flask Blueprint.
Now, in order to create the model classes I need to have a hold of the app, something like:
# apps.members.models
from flask import current_app
from flaskext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(current_app)
class Member(db.Model):
# fields here
pass
But if I try and import that model into my Blueprint app, I get the dreaded RuntimeError: working outside of request context. How can I get a hold of my app correctly here? Relative imports might work but they're pretty ugly and have their own context issues, e.g:
from ...site import app
# ValueError: Attempted relative import beyond toplevel package
The flask_sqlalchemy module does not have to be initialized with the app right away - you can do this instead:
# apps.members.models
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Member(db.Model):
# fields here
pass
And then in your application setup you can call init_app:
# apps.application.py
from flask import Flask
from apps.members.models import db
app = Flask(__name__)
# later on
db.init_app(app)
This way you can avoid cyclical imports.
This pattern does not necessitate the you place all of your models in one file. Simply import the db variable into each of your model modules.
Example
# apps.shared.models
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# apps.members.models
from apps.shared.models import db
class Member(db.Model):
# TODO: Implement this.
pass
# apps.reporting.members
from flask import render_template
from apps.members.models import Member
def report_on_members():
# TODO: Actually use arguments
members = Member.filter(1==1).all()
return render_template("report.html", members=members)
# apps.reporting.routes
from flask import Blueprint
from apps.reporting.members import report_on_members
reporting = Blueprint("reporting", __name__)
reporting.route("/member-report", methods=["GET","POST"])(report_on_members)
# apps.application
from flask import Flask
from apps.shared import db
from apps.reporting.routes import reporting
app = Flask(__name__)
db.init_app(app)
app.register_blueprint(reporting)
Note: this is a sketch of some of the power this gives you - there is obviously quite a bit more that you can do to make development even easier (using a create_app pattern, auto-registering blueprints in certain folders, etc.)
an original app.py: https://flask-sqlalchemy.palletsprojects.com/en/2.x/quickstart/
...
app = flask.Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = flask.ext.sqlalchemy.SQLAlchemy(app)
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
class Computer(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
# Create the database tables.
db.create_all()
...
# start the flask loop
app.run()
I just splitted one app.py to app.py and model.py without using Blueprint. In that case, the above answer dosen't work. A line code is needed to work.
before:
db.init_app(app)
after:
db.app = app
db.init_app(app)
And, the following link is very useful.
http://piotr.banaszkiewicz.org/blog/2012/06/29/flask-sqlalchemy-init_app/

Categories

Resources