I know there are a few threads out there with the same issue, but all of them have different ways they are initialising their flask applications, and the answers are sort of tailored to their setup. Some have put their SQLAlchemy init in their models files, some have just one massive application.py file with declarations at the top, some put it in __ init__.py.
But I've structured my flask applications the following way since I am comforable with this pattern:
init.py
| \ \ \ \
| \ \ \ \
app.py models.py init_db.py some_db_helper.py module_with_db_needs.py
Basically I have an init.py file that contains all the initialisation code for a Flask app:
init.py
from flask import Flask
from flask_wtf.csrf import CSRFProtect
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SECRET_KEY"] = "abcd"
db = SQLAlchemy()
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///testdata.sqlite3"
db.init_app(app)
csrf = CSRFProtect()
csrf.init_app(app)
From there, any other file that I need can just import the needed objects. So models.py can import db from init.py for defining models, app.py can import app for flask routes, and some_db_helper.py can also import db, etc:
models.py
from sqlalchemy import Table, ForeignKey, ...
from init import db, app # Import app, db from init file
class Category(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
# ... other properties
app.py
from init import app # Import app from init file
#app.route("/index.html", methods=["GET"])
def index():
return True
This file should create a new database file (I'm using sqlite), create the tables and data:
init_db.py
from models import * # Get app, db from models file
def insert_data():
# Insert some data into Category table
with app.app_context():
db.create_all()
insert_data()
However, when I run python init_db.py, my database file does get created, but it has no tables created at all. When the program tries to insert data, I get this error:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: category
I am aware that the db models need to be defined first, so I have my from models import * line, then doing create_all() at the end after all is loaded. You can also see init_db.py is pulling db from the import *, so it's using the same db instance as the models.py file, which means it should be aware of the defined models before creating the tables.
How can I get my init_db.py to create the tables properly using my current folder structure / import strategy?
I have read your comments, you have built some anti patterns, so rather than shoehorning this into working, let's go back to a healthier hierarchy.
. root of your app
+-- app.py (initialise app and routes)
+-- models.py (create models)
+-- init_db.py (db initial data, if you really don't want it in models.py)
app.py
All app initialisation, can be run with flask --app path/to/app.py run.
from flask import Flask
from init_db import insert_initial_data
from models import db
app = Flask(__name__)
app.config["SECRET_KEY"] = "abcd"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///testdata.sqlite3"
db.init_app(app)
with app.app_context():
db.create_all()
insert_initial_data()
#app.route("/index.html", methods=["GET"])
def index():
return b"OK"
models.py
All DB schema.
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, Table
db = SQLAlchemy()
class Category(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
...
init_db.py
I'd argue that belongs in models.py if you need them in the database on init.
def insert_initial_data():
...
PS1. If you want to split out your routes I really recommend a blueprint, it as easy to use as the Flask instance in your app.
PS2. This is functional, it does run and creates the DB file.
Related
I've read many of the other posts on having models separated from the main app but I can't get it to work with just app.py (my actual app) and models.py (my database models).
If I do the following I get an app.db file with no tables:
from app import db
db.create_all()
If I do the following I get a RuntimeError: No application found. Either work inside a view function or push an application context.:
from app import db
db.create_all()
I have also looked at Introduction into Contexts page and can't work out where I put def create_app():, none of it seemed to work in my case.
Here is my app.py:
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from models import userTable
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
#app.route('/', methods=['GET', 'POST'])
def home():
return "home"
if __name__ == '__main__':
app.run(debug=True)
Here is my models.py:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class userTable(db.Model):
__tablename__ = "userTable"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
Perhaps you could try the following:
app.py
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
import models
from models import initialize_db
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
initialize_db(app)
#app.route('/', methods=['GET', 'POST'])
def home():
return "home"
if __name__ == '__main__':
app.run(debug=True)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def initialize_db(app):
app.app_context().push()
db.init_app(app)
db.create_all()
class userTable(db.Model):
__tablename__ = "userTable"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
The key is creating a database initialization function within models.py which takes the application instance as a parameter. This function only creates the database tables once it has its application instance. This will allow you to import the models module initially without an application instance and still have a modular design.
When testing my Pyramid application using WebTest, I have not been able to create/use a separate Session in my tests without getting warnings about a scoped session already being present.
Here is the main() function of the Pyramid application, which is where the database is configured.
# __init__.py of Pyramid application
from pyramid_sqlalchemy import init_sqlalchemy
from sqlalchemy import create_engine
def main(global_config, **settings):
...
db_url = 'some-url'
engine = create_engine(db_url)
init_sqlalchemy(engine) # Warning thrown here.
Here is the test code.
# test.py (Functional tests)
import transaction
from unittest import TestCase
from pyramid.paster import get_appsettings
from pyramid_sqlalchemy import init_sqlalchemy, Session
from sqlalchemy import create_engine
from webtest import TestApp
from app import main
from app.models.users import User
class BaseTestCase(TestCase):
def base_set_up(self):
# Create app using WebTest
settings = get_appsettings('test.ini', name='main')
app = main({}, **settings)
self.test_app = TestApp(app)
# Create session for tests.
db_url = 'same-url-as-above'
engine = create_engine(db_url)
init_sqlalchemy(engine)
# Note: I've tried both using pyramid_sqlalchemy approach here and
# creating a "plain, old" SQLAlchemy session here using sessionmaker.
def base_tear_down(self):
Session.remove()
class MyTests(BaseTestCase):
def setUp(self):
self.base_set_up()
with transaction.manager:
self.user = User('user#email.com', 'John', 'Smith')
Session.add(self.user)
Session.flush()
Session.expunge_all()
...
def tearDown(self):
self.base_tear_down()
def test_1(self):
# This is a typical workflow on my tests.
response = self.test_app.patch_json('/users/{0}'.format(self.user.id), {'email': 'new.email#email.com')
self.assertEqual(response.status_code, 200)
user = Session.query(User).filter_by(id=self.user.id).first()
self.assertEqual(user.email, 'new.email#email.com')
...
def test_8(self):
...
Running the tests gives me 8 passed, 7 warnings, where every test except the first one gives the following warning:
From Pyramid application: __init__.py -> main -> init_sqlalchemy(engine):
sqlalchemy.exc.SAWarning: At least one scoped session is already present. configure() can not affect sessions that have already been created.
If this is of any use, I believe I am seeing the same issue here, except I am using pyramid_sqlalchemy rather than creating my own DBSession.
https://github.com/Pylons/webtest/issues/5
Answering my own question: I'm not sure if this is the best approach, but one that worked for me.
Instead of trying to create a separate session within my tests, I am instead using the pyramid_sqlalchemy Session factory, which is configured in the application. As far as I can tell, calls to Session in both test and application code return the same registered scoped_session.
My original intent with creating a separate session for my tests was to confirm that records were being written to the database, and not just updated in the active SQLAlchemy session. With this new approach, I've managed to avoid these "caching" issues by issuing Session.expire_all() at points in the tests where I transition between test transactions and application transactions.
# test.py (Functional tests)
import transaction
from unittest import TestCase
from pyramid.paster import get_appsettings
from pyramid_sqlalchemy import Session
from webtest import TestApp
from app import main
from app.models.users import User
class BaseTestCase(TestCase):
def base_set_up(self):
# Create app using WebTest
settings = get_appsettings('test.ini', name='main')
app = main({}, **settings)
self.test_app = TestApp(app)
# Don't set up an additional session in the tests. Instead import
# and use pyramid_sqlalchemy.Session, which is set up in the application.
def base_tear_down(self):
Session.remove()
class MyTests(BaseTestCase):
def setUp(self):
self.base_set_up()
with transaction.manager:
self.user = User('user#email.com', 'John', 'Smith')
Session.add(self.user)
Session.flush()
Session.expunge_all()
Session.expire_all() # "Reset" your session.
def tearDown(self):
self.base_tear_down()
Ello ello,
I found similar questions on the bug i'm facing, and tried the solutions offered but it didn't work for me.
I'm trying to separate out my models in a different directory and import them into the app.py
When I try to import the db into the python terminal, i'm getting the no application found.
app.py code
from flask import Flask
from flask_restful import Resource, Api
# from flask_sqlalchemy import SQLAlchemy
from routes import test, root, user
from models.todo import db
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:pass123#localhost/db'
app.config['SECRET_KEY'] = 'thiskeyissecret'
# db.init_app(app)
with app.app_context():
api = Api(app)
db.init_app(app)
api.add_resource(root.HelloWorld, '/')
api.add_resource(test.Test, '/test')
api.add_resource(user.User, '/user')
if __name__ == '__main__':
app.run(debug=True)
models
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Todo(db.Model):
__tablename__ = 'Todos'
id = db.Column('id', db.Integer, primary_key=True)
data = db.Column('data', db.Unicode)
def __init__(self, id, data):
self.id = id
self.data = data
def __repr__(self):
return '<Todo %>' % self.id
my file directory looks like
Main_app
Models
Todo.py
routes
some routes
app.py
Flask-SQLAlchemy needs an active application context.
Try:
with app.app_context():
print(Todo.query.count())
From the flask documentation:
Purpose of the Context
The Flask application object has attributes, such as config, that are
useful to access within views and CLI commands. However, importing the
app instance within the modules in your project is prone to circular
import issues. When using the app factory pattern or writing reusable
blueprints or extensions there won’t be an app instance to import at
all.
Flask solves this issue with the application context. Rather than
referring to an app directly, you use the the current_app proxy, which
points to the application handling the current activity.
Flask automatically pushes an application context when handling a
request. View functions, error handlers, and other functions that run
during a request will have access to current_app.
It is ok to have db initialised in app.py
from flask import Flask
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
from routes import test, root, user
app = Flask(__name__)
api = Api(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:pass123#localhost/db'
app.config['SECRET_KEY'] = 'thiskeyissecret'
db = SQLAlchemy(app)
api.add_resource(root.HelloWorld, '/')
api.add_resource(test.Test, '/test')
api.add_resource(user.User, '/user')
if __name__ == '__main__':
app.run(debug=True)
Then in your todo.py
from app import db
class Todo(db.Model):
__tablename__ = 'Todos'
id = db.Column('id', db.Integer, primary_key=True)
data = db.Column('data', db.Unicode)
def __init__(self, id, data):
self.id = id
self.data = data
def __repr__(self):
return '<Todo %>' % self.id
I get a same err
that err reason for just can operation db in viewfunc
def __init__(self, id, data):
self.id = id
self.data = data
try move that code operation to your viewfunc
In a nutshell, do something like this:
from yourapp import create_app
app = create_app()
app.app_context().push()
What am I doing?
I had been learning from a Flask tutorial (https://blog.miguelgrinberg.com/) and I'm stuck on the ORM section as it uses SQLAlchemy and I want to use Peewee (I use it in another project, and had been working nicely on it).
Noob Problematic
After configuring Peewee and define a BaseModel, and implementing it in a User model, I tried to do a query and got this error instead:
File "/home/atrevino/.local/share/virtualenvs/comparteme-qStpFUrM/lib/python3.6/site-packages/peewee.py", line 2932, in compiler
return self.database.compiler()
AttributeError: 'Flask' object has no attribute 'compiler'
Not sure if this is related to the configuration of my database, but I was able to get the MySQLDatabase object which makes me to think it's doing a connection to the DB properly.
The error above is displayed whenever I try to do something with Peewee's models, for example User.get(User.id == 1) or User.create(username='Name')
This is my test-app and environment configuration
./config.py
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-know'
DATABASE = {
'name': 'comparteme',
'engine': 'peewee.MySQLDatabase',
'user': 'root',
'password': 'whatever',
'host': os.environ.get('DATABASE_URL') or 'mysql://root#localhost:3306'
}
comparteme/__init__.py
import peewee
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
db = peewee.MySQLDatabase(app).database
from comparteme import routes
comparteme/models/base_model.py
import peewee
from comparteme import db
class BaseModel(peewee.Model):
class Meta:
database = db
class User(BaseModel):
username = peewee.CharField(unique=True)
I call all this from a flask route:
comparteme/routes.py
from comparteme import app
from comparteme.models.base_model import User
#app.route('/')
#app.route('/index')
def index():
User.create(username='Alan')
return 'returning any string'
To be able to test I have one test.py on my root directory that I added to FLASK_APP environment variable by using export FLASK_APP=test.py.
./test.py
from comparteme import app
After that I just do a regular flask run and open a browser with http://127.0.0.1:5000/ and get the error.
Is this the correct way to configure Peewee?
Thanks in advance !!!
I am developing a Flask 0.12 app with Blueprints and I am trying to use Flask-JWT (0.3.2) for authentication.
None of the tutorials that I can see for Flask-JWT use Blueprints.
There is a usually a block of code in each example, that does something like this:
app = Flask(__name__)
jwt = JWT(app, authenticate, identity)
In a blueprinted flask app the structure tends to be more like this:
__init__.py:
from swarm.controllers.main import main
from swarm.controllers.grid import grid
from swarm.controllers.address import address
def create_app(object_name):
app = Flask(__name__)
...
app.register_blueprint(main)
app.register_blueprint(grid)
app.register_blueprint(address)
controllers/main.py:
main = Blueprint('main', __name__)
controllers/grid.py:
grid = Blueprint('grid', __name__)
controllers/address.py:
address = Blueprint('address', __name__)
How would I reference Flask-JWT and use its decorators in the controllers?
It might prove easier to answer this question by showing how to add JWT decorators to a standard example such as blueprintexample in the flask source code o Michał Karzyński's REST API demo.
I've built a simple example that enables use of Flask-JWT decorators.
The file structure:
server
├── app
| ├── admin # blueprint
| | ├── __init__.py
| | ├── views.py
| ├── __init__.py
├── config.py
└── run.py
First, create a blueprint called admin and import its views
# file: server/app/admin/init.py
from flask import Blueprint
admin = Blueprint('admin', __name__)
from . import views
The admin blueprint has one view. Here, we import the admin blueprint and some flask_jwt stuff so we can properly access their decorators:
from flask_jwt import jwt_required, current_identity
from . import admin
# file: server/app/admin/views.py
#admin.route('/protected')
#jwt_required()
def protected():
return '%s' % current_identity
Now create the flask app, create the JWT instance, and register the admin blueprint to the flask application:
# file: server/app/__init__.py
from flask import Flask
from flask_jwt import JWT
from werkzeug.security import safe_str_cmp
from .admin import admin
# skipping over jwt authenticate() and identity() creation
# https://pythonhosted.org/Flask-JWT/
# ...
app = Flask(__name__)
app.config.from_object('config')
jwt = JWT(app, authenticate, identity)
app.register_blueprint(admin, url_prefix='/admin')
And finally, set up your script manager:
from flask_script import Manager, Server
from server.app import app
manager = Manager(app)
# Turn on debugger by default and reloader
manager.add_command("runserver", Server(
use_debugger = True,
use_reloader = True,
host = '0.0.0.0',
port = 5000)
)
if __name__ == "__main__":
manager.run()
Now, if we go to our /admin/protected url, we will see that JWT is up and running:
{
"description": "Request does not contain an access token",
"error": "Authorization Required",
"status_code": 401
}
I hope this helps!