I am confused about how to use Flask-Migrate when I have multiple models.
Basically my Flask app looks like this:
app
├── __init__.py
├── config.py
├── manage.py
├── migrations
├── models
│ ├── model1.py
│ ├── model2.py
├── resources
├── run.py
└── tests
I've read that for each model its best to create the db = SQLAlchemy() object in the file and then import this db object into the app's__init__.py like so:
from models.model1 import db
db.init_app(app)
from models.model2 import db
db.init_app(app)
However if I do this for multiple model files, how can I add Flasks's migrate functionality, considering I can only use 1 sql alchemy object for the migrate class instantiation:
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
Would it be best in this case to define a single sql alchemy object in the __init__.py file and import that into all my models?
You misread the referenced text. That talks about something completely different. That talks about keeping your db object separate from the app object (and tie the two togther in the create_app factory function). Creating multiple db objects is only complicating matters for you.
All that is needed is a single db = SQLAlchemy() object, and all the files that define models need to be imported. Usually that's done directly or indirectly via your create_app factory function, You need to call the create_app() function anyway to be able to run the flask db command-line tool anyway.
Next, you do not need to create a manager either. The Manager object is a hold-over from the time before the Flask project added support for scripts itself. If you are using Flask 0.12 or newer, you don't want to be using Flask-Script and it's manager.
So, all you need, in your __init_.py, is:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
def create_app(test_config=None):
app = Flask(__name__)
app.config.from_object(f"{__name__}.config")
app.config.from_envvar("PROJECTNAME_SETTINGS", silent=True)
if test_config:
app.config.from_mapping(test_config)
db.init_app(app)
Migrate(app, db)
# importing the models to make sure they are known to Flask-Migrate
from models import models1, models2
# any other registrations; blueprints, template utilities, commands
return app
Related
when I run the Flask Server with flask run, I get error 404 in the Index Page.
* Serving Flask app "sf.py"
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Feb/2021 10:25:56] "GET / HTTP/1.1" 404 -
Not Found
The requested URL was not found on the server. If you entered the URL
manually please check your spelling and try again.
Project Structure
.
├── app
│ ├── models.py
│ ├── routes.py
│ └── __init__.py
├── clients
│ └── client.py
├── migrations
├── tests
│ ├── conftest.py
│ ├── test_models.py
│ ├── test_client.py
│ └── __init__.py
├── publisher.py
├── manage.py
├── run_client.py
├── requirements.txt
└── sf.py
/sf.py
from app import create_app
create_app()
/app/__init__.py
from flask import Flask
from . models import db
POSTGRES = {
'user': 'sf',
'pw': 'sf',
'db': 'sf',
'host': 'localhost',
'port': '5432',
}
def create_app():
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s:%(pw)s#%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
return app
from app import routes
/app/routes.py
from app import create_app
from app.models import Area, Sensor, Monitoring
from flask import request, jsonify
from flask.views import MethodView
app = create_app()
#app.route('/')
def hello_world():
return 'Hello, World!'
...
I need to use the create_app() because I need an that the /clients/client.py use the app.
/clients/client.py
from paho.mqtt.client import Client
import json
from app import create_app
from app.models import db
from app.models import Monitoring
app = create_app()
class CustomClient(Client):
def add_reading(self, reading):
with app.app_context():
db.session.add(reading)
db.session.commit()
def on_connect(self, client, userdata, flags, rc):
print(
"Connected:",
str(client._host) + ":" + str(client._port)
)
def on_subscribe(self, mqttc, obj, mid, granted_qos):
print(
"Subscribed:",
str(mid), str(granted_qos)
)
def on_message(self, client, userdata, message):
msg = message.payload.decode()
print(message.topic, msg)
data = json.loads(msg)
reading = Monitoring(**data)
self.add_reading(reading)
def run(self):
self.connect("localhost", 1883, 60)
self.subscribe("Main/#", 0)
self.loop_forever()
But in this way I get the 404 error. And I'm not sure that I'm using the app properly. It would be fine to have an app and a db session separate, to test models and client without care the app configuration (probably I need to create a separate config for test?). What I've missed?
You are creating three instances of the Flask() object. One is created in sf.py, the others in routes.py and client.py. The first one is used to serve the site, and so doesn't have your route, because the route is registered with the instance created in routes.py. The 3rd instance, in client.py is independent and isn't further altered, so is not an issue here; more on that below.
Don't create multiple copies, at least not and alter the registrations on one and expect those to be available on the other. Instead, use blueprints to register your views, and then register the blueprint with the Flask() object in your create_app() function. That way you can decouple registration of your routes from creating the Flask() object, and still get your routes registered centrally.
In your routes.py, use:
from app.models import Area, Sensor, Monitoring
from flask import Blueprint, request, jsonify
from flask.views import MethodView
bp = Blueprint('main', __name__)
#bp.route('/')
def hello_world():
return 'Hello, World!'
# ...
and then import that blueprint in create_app():
def create_app():
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s:%(pw)s#%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
from . import routes
app.register_blueprint(routes.bp)
return app
The reason you want to do the import in create_app() is that in most Flask applications you'll also be using one or more Flask extensions that are generally created outside of create_app() so your views can import them. You'd get a circular import if you tried to import one of those objects in your routes module if your routes module was imported into app.py at the top level.
With this change (to using a blueprint), you avoid creating a separate Flask() instance with registrations that the main instance, used for serving your site, won't see. Even your client.py process will be able to access those routes now, should there be a need (e.g. if you need to generate URLs with url_for()).
Here is an example from an in-production Flask project I built for a client recently, the app.py module contains, in part, the following code:
from flask import Flask
from flask_babel import Babel
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_security import Security, SQLAlchemyUserDatastore
from flask_sqlalchemy import SQLAlchemy
babel = Babel()
db = SQLAlchemy()
ma = Marshmallow()
migrate = Migrate()
security = Security()
_app_init_hooks = []
app_init_hook = _app_init_hooks.append
def create_app():
app = Flask(__name__)
for f in _app_init_hooks:
f(app)
return app
#app_init_hook
def _configure(app):
"""Load Flask configurations"""
app.config.from_object(f"{__package__}.config")
# optional local overrides
app.config.from_pyfile("settings.cfg", silent=True)
app.config.from_envvar("PROJECT_NAME_SETTINGS", silent=True)
#app_init_hook
def _init_extensions(app):
"""Initialise Flask extensions"""
if app.env != "production":
# Only load and enable when in debug mode
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
# Python-level i18n
babel.init_app(app)
# Database management (models, migrations, users)
from .models import Role, User
db.init_app(app)
migrate.init_app(app, db)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security.init_app(app, user_datastore)
# Marshmallow integration (must run after db.init_app())
ma.init_app(app)
#app_init_hook
def _setup_blueprints(app):
"""Import and initialise blueprints"""
from . import users
from .sections import BLUEPRINTS
for blueprint in (*BLUEPRINTS, users.bp):
app.register_blueprint(blueprint)
return app
I've broken up the various components into separate functions to ease readability and maintainability, there are separate blueprints used for distinct site functions (which drives some automation in the UI).
At the top of the module are several Flask extensions that various routes and other modules need access to without having to worry about circular imports, so the blueprints are imported separately inside of the _setup_blueprints() hook function that is called from create_app().
Your use of create_app() in client.py should be fine because it doesn't add any new configuration to the Flask() instance that you'd want to have access to elsewhere, and presumably client.py is used outside of the Flask webserver process. But I would, personally, just make the result of create_app()an instance attribute of your Client instance. You don't need a global there, you only need it to access the database session easily for when add_reading() is called:
class CustomClient(Client):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs):
# Create a Flask context so we can access the SQLAlchemy session
self._app = create_app()
def add_reading(self, reading):
with self._app.app_context():
db.session.add(reading)
db.session.commit()
# ...
If add_reading() is called very frequently, you could consider making app an instance attribute of CustomClient():
Am currently studying how to develop web applications using Flask and i was following this tutorial on how to do it and everything was moving on fine till the concept of Flask-SQLAchemy got introduced.
The structure of my flask application is as below
├── bin
├── bookshelf
│ ├── admin
│ │ ├── controllers.py
│ │ ├── __init__.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── models.py
│ ├── __init__.py
│ ├── main
│ │ ├── controllers.py
│ │ ├── __init__.py
│ ├── static
│ └── templates
├── config.py
├── data-dev.sqlite
├── docs
├── requirements.txt
├── run.py
Am trying to use an sqlite database(data-dev.sqlite) that i designed myself without having to go through the process of SQLAchemy creating for me one and below is it's structure; that was generated using the .dump command in sqlite
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE author (
id INTEGER NOT NULL PRIMARY KEY,
author_names VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE book (
id INTEGER NOT NULL PRIMARY KEY,
title VARCHAR(80) NOT NULL,
rating INT,
image VARCHAR(30),
author_id INTEGER NOT NULL,
FOREIGN KEY (author_id) REFERENCES author(id)
);
CREATE TABLE role (
id INTEGER NOT NULL PRIMARY KEY,
role_name VARCHAR(80) NOT NULL UNIQUE,
description VARCHAR(255)
);
CREATE TABLE user (
id INTEGER NOT NULL PRIMARY KEY,
email VARCHAR(75) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
active BOOLEAN NOT NULL DEFAULT 0,
role_id INTEGER NOT NULL,
FOREIGN KEY (role_id) REFERENCES role(id)
);
COMMIT;
The traceback produced when i run the command python run.py runserver -d in the venv is
Traceback (most recent call last):
File "run.py", line 1, in <module>
from bookshelf import create_app
File "/home/mayatsa/environments/flask-test/bookshelf/__init__.py", line 2, in <module>
from bookshelf.main.controllers import main
File "/home/mayatsa/environments/flask-test/bookshelf/main/controllers.py", line 2, in <module>
from bookshelf.data.models import Author, Book
File "/home/mayatsa/environments/flask-test/bookshelf/data/models.py", line 6, in <module>
class Book(db.Model):
File "/home/mayatsa/environments/flask-test/bookshelf/data/models.py", line 8, in Book
__table__ = db.Model.metadata.tables['book']
KeyError: 'book'
Files order of traceback from top to bottom and their contents are as follows
run.py
from bookshelf import create_app
from flask_script import Manager
app = create_app()
manager = Manager(app)
if __name__ == '__main__':
manager.run()
bookshelf/__init__.py
from flask import Flask
from bookshelf.main.controllers import main
from bookshelf.admin.controllers import admin
from bookshelf.data.models import db
def create_app():
app = Flask(__name__)
app.config.from_object('config')
with app.app_context():
db.init_app(app)
db.Model.metadata.reflect(db.engine)
app.register_blueprint(main, url_prefix='/')
app.register_blueprint(admin, url_prefix='/admin')
return app
bookshelf/main/controllers.py
from flask import Blueprint, render_template, request
from bookshelf.data.models import Author, Book
main = Blueprint('main', __name__, template_folder='templates')
#main.route('/')
def index():
return render_template('main/index.html')
#main.route('books/')
def display_books():
return render_template('main/books.html')
bookshelf/data/models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Book(db.Model):
__table__ = db.Model.metadata.tables['book']
def __repr__(self):
return '<Book %r>' % (self.title)
class Author(db.Model):
__table__ = db.Model.metadata.tables['author']
def __repr__(self):
return '<Author %r>' % (self.author_names)
class Role(db.Model):
__table__ = db.Model.metadata.tables['role']
def __repr__(self):
return '<Role %r>' % (self.role_name)
class User(db.Model):
__table__ = db.Model.metadata.tables['user']
def __repr__(self):
return '<User %r>' % (self.email)
Additional config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
TESTING = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = 'a9eec0e0-23b7-4788-9a92-318347b9a39f'
From all the code provided above, what could be causing my application to produce this error and stopping it from running?
Thank you
I kept probing till i landed on this question and this one and they helped clear up the fog for me.
After installing sqlacodegen and flask-sqlacodegen using
pip install sqlacodegen
pip install flask-sqlacodegen
All i had to do now is run the following command in my Flask application root
sqlacodegen sqlite:///data-dev.sqlite --flask > models.py
And the models corresponding to my database were generated and then after i replaced my the bookshelf/data/models.py with the just generated models.py.
Then finally changed bookshelf/__init__.py create_app function to look like this
def create_app():
app = Flask(__name__)
app.config.from_object('config')
db.init_app(app)
app.register_blueprint(main, url_prefix='/')
app.register_blueprint(admin, url_prefix='/admin')
return app
And now everything seems to be working fine.
This is because the models.py file is executed before the tables are reflected. In bookshelf/__init__.py file, you're importing your controllers main and admin at the top, which in turn import your data models from bookshelf/data/models.py which subclasses the SQLAlchemy instance db. But the db.reflect() is yet to happen, therefore the metadata of the tables isn't reflecting and hence the key error. To solve this, either import your models in your controllers inside the functions, wherever needed, like this
bookshelf/main/controllers.py
from flask import Blueprint, render_template, request
main = Blueprint('main', __name__, template_folder='templates')
#main.route('/')
def index():
from bookshelf.data.models import Author, Book
return render_template('main/index.html')
#main.route('books/')
def display_books():
from bookshelf.data.models import Book
return render_template('main/books.html')
or import your controllers after you've initialized your db object like this,
bookshelf/init.py
from flask import Flask
from bookshelf.data.models import db
def create_app():
app = Flask(__name__)
app.config.from_object('config')
with app.app_context():
db.init_app(app)
db.Model.metadata.reflect(db.engine)
from bookshelf.admin.controllers import admin, main
app.register_blueprint(main, url_prefix='/')
app.register_blueprint(admin, url_prefix='/admin')
return app
After this, just add a separate database.py file to create the SQLAlchemy object instead of creating it in your models.py file, since it will cause a circular import issue if you have it in either bookshelf/init.py or bookshelf/data/models.py.
bookshelf/database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Remove this line from your init.py file
from bookshelf.data.models import db
and instead get it in your init.py from database.py
from bookshelf.database import db
The metadata isn't there yet at the time you're importing bookshelf.data.models, as it's being reflected into the metadata only afterwards.
Maybe using __tablename__ instead of __table__ might help? I'm not that well versed in the intricacies of SQLAlchemy.
+-- myproject
¦
+-- api
¦ +-- tests
¦ +-- app
¦ +-- blueprint_1
¦ +-- blueprint_2
¦
+-- data_collector
+-- tests
+-- app
+-- scraper
My json api project using Flask and flask-sqlalchemy is structured as the above. The data_collector app I plan to setup to run automatically every hour to scrape the latest hourly data published on a webpage, then populate a database with this data. The api app I want to use to return json according to queries specified in the blueprints.
So far I have been able to populate an sqlite database using the data_collector app but I can only query the database from within this app, not the api app. I have been reading the flask-sqlalchemy docs and reading up elsewhere. For starters, is there a better way I should be structuring my project?
My data_collector app instantiates the database as follows:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
I am not able to access the db or app object from outside this file. So I cannot put my models in a separate file since my models have db.Models as the base class. From the docs I think I need to have the following instead of the above:
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
I have tried this in both my apps, but still get the same error: 'Cannot import app from app' or 'Cannot import db from app'. I am not sure my app within the api directory will need access to app from data_collector since it is independent, but I am still not able to access the sqlite database that I created. How can I access the database.db file created using data_collector from a separate app (I have tried as best I can to follow info in the docs and elsewhere on the internet)?
You can just bind db to app manually.
# db.py
from flask_sqlalchemy import SQLAlchemy
def init_app(app):
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
app.db = db
# __init__.py
from flask import Flask
from . import db
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
Most flask applications will instantiate a db and app as a global variable and import them in other files. I don't think we should import static variables (except for constants). I think a more pythonic way is to import modules.
I am following the book Mastering flask's recommended file structure.
(The name of my project is Paw)
In Paw/paw/__init__.py:
def create_app(object_name):
app = Flask(__name__)
app.config.from_object(object_name)
db.init_app(app)
robot = LoggingWeRobot(token='banana', enable_session=True)
robot.init_app(app, endpoint='werobot', rule='/wechat')
attach_debugging_logger(app)
app.register_blueprint(poll_blueprint)
app.register_blueprint(wechat_blueprint)
return app
Note that the robot variable is actually needed in my blueprint, wechat, found in: Paw/paw/controllers/wechat.py
#robot.handler
def request_logging_middleware(message, session):
app.logger.debug("\n%s", request.data)
return False # This allows other handlers to continue execution
So my problem is that my blueprint has no access to the robot variable. However, the robot variable should be created in create_app in Paw/paw/__init__.py because I am trying to follow the application factory pattern.
Any recommendation on how to fix this? My project can be found here and I am trying to follow this application structure
Simply use the same pattern you are using for db - create robot elsewhere and import it into your Paw/paw/__init__.py file, just as you do with db:
import db from models
import robot from wechat_setup
# wechat_setup is where you would invoke
# robot = LoggingWeRobot(token='banana', enable_session=True)
def create_app(object_name):
app = Flask(__name__)
app.config.from_object(object_name)
db.init_app(app)
robot.init_app(app, endpoint='werobot', rule='/wechat')
I usually put project global variables in one file (say gvars.py).
Then the project structure will be some sort like this:
.
├── etc
│ └── webapp.py
├── models
│ └── common.py
├── views
│ └── common.py
├── gvars.py
└── webapp.py
In other files we just do this:
from gvars import db, robot # or other variables
I have a flask application which uses jinja2 template filters. An example of template filter is as follows:
#app.template_filter('int_date')
def format_datetime(date):
if date:
return utc_time.localize(date).astimezone(london_time).strftime('%Y-%m-%d %H:%M')
else:
return date
This works fine if we have an app instantiated before a decorator is defined, however if we are using an app factory combined with flask-script manager, then we don't have an instantiated app. For example:
def create_my_app(config=None):
app = Flask(__name__)
if config:
app.config.from_pyfile(config)
return app
manager = Manager(create_my_app)
manager.add_option("-c", "--config", dest="config", required=False)
#manager.command
def mycommand(app):
app.do_something()
Manager accepts either an instantiated app or an app factory, so at first glance it appears that we can do this:
app = create_my_app()
#app.template_filter('int_date')
....
manager = Manager(app)
The problem with this solution is that the manager then ignores the option, since the app has already been configured during instantiation. So how is someone supposed to use template filters together with the flask-script extension?
This is where blueprints come into play. I would define a blueprint core and put all my custom template filters in say core/filters.py.
To register filters to an application in flask when using blueprints you need to use app_template_filter instead of template_filter. This way you can still use the decorator pattern to register filters and use the application factory approach.
A typical directory layout for an application using blueprint might look something like:
├── app
│ ├── blog
│ │ ├── __init__.py # blog blueprint instance
│ │ └── routes.py # core filters can be used here
│ ├── core
│ │ ├── __init__.py # core blueprint instance
│ │ ├── filters.py # define filters here
│ │ └── routes.py # any core views are defined here
│ └── __init__.py # create_app is defined here & blueprint registered
└── manage.py # application is configured and created here
For a minimal working example of this approach see: https://github.com/iiSeymour/app_factory
The solution can be found here, which states they are two ways one can define a jinja template filter. Thus, instead of defining a decorator outside the factory, one can modify the jinja_env instead. This can be done in the app factory, for example:
def format_datetime(date):
if date:
return utc_time.localize(date).astimezone(london_time).strftime('%Y-%m-%d %H:%M')
else:
return date
def create_app(production=False):
app = Flask(__name__)
....
# Register Jinja2 filters
app.jinja_env.filters['datetime'] = format_datetime
manager = Manager(create_app)
...