Stream data to database for Flask app to consume [duplicate] - python

This question already has answers here:
flask make_response with large files
(2 answers)
Closed 3 years ago.
I'm fairly new to this task so please help me identify the concepts I'm missing.
I'm trying to stream data from an API to my SQLite db and let a Flask app consume the data. I defined the models in models.py like this
# models.py
import os
from flask_sqlalchemy import SQLAlchemy
# Create sqlite db
db = SQLAlchemy()
class MyDataModel(db.Model):
# Manual table name choice
__tablename__ = 'table1'
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.Text)
text = db.Column(db.Text)
def __init__(self, created_at, text):
self.created_at = created_at
self.text = text
def __repr__(self):
return f"Data: {self.text} ... created at {self.created_at}"
In app.py I have a simple view function that counts the rows and return a Server Sent Event to the frontend for realtime tracking.
# app.py
import os
import time
from flask import Flask, render_template, url_for, redirect, Response
from flask_migrate import Migrate
from models import db, MyDataModel
from settings import *
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
Migrate(app, db)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/getcount')
def getcount():
def count_stream():
count = db.session.query(MyDataModel).count()
while True:
yield f"data:{str(count)}\n\n"
time.sleep(0.5)
return Response(count_stream(), mimetype='text/event-stream')
if __name__ == "__main__":
app.run(debug=True, port=PORT)
Now I have another python script stream_to_db.py that gets data from an API as a stream, approximately like this
# stream_to_db.py
import logging
import os
from models import db, MyDataModel
from settings import *
from SomeExternalAPI import SomeAPI
def stream_to_db():
api = SomeAPI(
API_KEY, API_SECRET_KEY, ACCESS_TOKEN, ACCESS_TOKEN_SECRET
)
r = api.request()
for item in r:
created_at, text = item['created_at'], item['text']
logging.info(text)
datum = MyDataModel(created_at, text)
db.session.add(datum)
db.session.commit()
# Stream data to sqlite
stream_to_db()
When I try to run this python stream_to_db.py I get error
RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.
I looked at the documentation of application context but am still confused. Say I go without SQLAlchemy and use Python and SQL directly for data insertion, this stream_to_db.py script should be independent of the Flask app. But if I still want to leverage SQLAlchemy for its syntax and model definitions from models.py, how should I do it?
Conceptually I feel the streaming to db part is independent of the Flask app and should be a script that's essentially a while True loop and goes forever. The Flask app just reads the db, send data to frontend and does nothing else. I tried put the stream_to_db() function into __main__ in app.py but that doesn't make sense, app.run() and stream_to_db() are both essentially while True loops and can't be put together.
I'm lost and miss key concepts here. Please help and suggest the right way/best practices to do it. I feel this is a really basic task which should have a best practice and a set of dedicated tools already. Thanks in advance!
EDIT
To further experiment, I imported app into stream_to_db.py and added
with app.app_context():
stream_to_db()
Now I can run python stream_to_db.py with no problem, but if I start the Flask app at the same time, I get several
Debugging middleware caught exception in streamed response at a point where response headers were already sent.
and also
Traceback (most recent call last):
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/werkzeug/wsgi.py", line 507, in __next__
return self._next()
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py", line 45, in _iter_encoded
for item in iterable:
File "/Users/<username>/webapps/<appname>/app.py", line 33, in count_stream
count = db.session.query(CardanoTweet).count()
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/sqlalchemy/orm/scoping.py", line 162, in do
return getattr(self.registry(), name)(*args, **kwargs)
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/sqlalchemy/util/_collections.py", line 1012, in __call__
return self.registry.setdefault(key, self.createfunc())
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/sqlalchemy/orm/session.py", line 3214, in __call__
return self.class_(**local_kw)
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 136, in __init__
self.app = app = db.get_app()
File "/Users/<username>/webapps/<appname>/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 982, in get_app
'No application found. Either work inside a view function or push'
RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.
Looks like SQLAlchemy can't find the application. Not sure if it's a problem about the db.init_app(app) in app.py and db = SQLAlchemy() in models.py. I avoided having db = SQLAlchemy(app) in models.py because I can't import app into models for the circular dependency reason.
EDIT2
This time I moved all the code from models.py to app.py and used db = SQLAlchemy(app), removed db.init_app(app), kept the import app to stream_to_db.py and the app context in it, and it worked!
My questions
How to correctly move the model definitions to models.py with no circular dependency?
What's the implication of having app context in stream_to_db.py? If I have gunicorn running many workers, there will essentially be multiple Flask app instances. Would that cause a problem?
EDIT3
Thanks for all the replies. I don't think this is a duplicate to flask make_response with large files.
The problem is not streaming data FROM Flask to the client, it is to stream data from external API to DB, and have Flask consume some statistics from the data in DB. So, I don't see why conceptually the streaming job should be related to Flask at all, they are independent. The problem is that I'm using SQLAlchemy in the streaming job for data model and db transaction, and SQLAlchemy needs Flask app defined. This part is where I get confused about.
What is the right way to write this ever-running background job that streams data to db, with data models defined with SQLAlchemy? Should I strip out the SQLAlchemy code from streaming code and just use SQL, and manually make sure the schema agrees if there are further migration down the road?

Please try following change in models.py where you've created db.
db = SQLAlchemy(app)

Related

Python Flask framework. AssertionError: A setup function was called after the first request was handled

I've been following the flask tutorial to add a database, in their example they're using mysqlite while I'm using MySQL but I figured that shouldn't make a huge difference here. https://flask.palletsprojects.com/en/1.1.x/tutorial/database/
Here's the mysql library I'm trying to use https://flask-mysql.readthedocs.io/en/stable/
However whatever I do I can't seem to get away from this assertion error:
AssertionError: A setup function was called after the first request was handled. This usually indicates a bug in the application where a module was not imported and decorators or other functionality was called too late.
To fix this make sure to import all your view modules, database models and everything related at a central place before the application starts serving requests.
The error only happens when trying to use the /dbtest path.
Here's my minimal __init__.py
import os
from flask import Flask, render_template
from . import db
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
MYSQL_DATABASE_HOST='mysql-svc',
MYSQL_DATABASE_USER='root',
MYSQL_DATABASE_PASSWORD='root',
DEBUG=True,
TESTING=True
)
db.init_app(app)
# a simple page that says hello
#app.route('/hello')
def hello():
return 'Hello, World!'
#app.route('/dbtest')
def db_test():
with app.app_context():
cursor = db.get_cursor()
cursor.execute('SHOW DATABASES')
return cursor.fetchall()
return app
And here is db.py which is in the same directory
from flaskext.mysql import MySQL
from flask import current_app, g
def get_cursor():
if 'db_cursor' not in g:
mysql = MySQL()
mysql.init_app(current_app)
g.db_cursor = mysql.get_db().cursor()
return g.db_cursor
def close_db(e=None):
db = g.pop('db_cursor', None)
if db is not None:
db.close()
def init_app(app):
app.teardown_appcontext(close_db)
Everything I've read about this error seems to indicate that I'm somehow messing around with routing after I've tried to access the database or something? But I'm just following the tutorial for how the routing is set up. I'm probably missing something obvious here, I believe it's an issue with mysql.init_app now but I still can't figure out how to do this.
The issue may be related if DEBUG = True, just ran into this myself came here looking for a solution. The closed github ticket below has a comment indicating that it was an issue at some point. As of today it still appears to be an issue.
https://github.com/ga4gh/ga4gh-server/issues/791
In the end I had to say screw it to the whole g thing with getting the cursor, same goes for closing the db with a teardown. In the end it works with just this in my db.py:
from flaskext.mysql import MySQL
def init_db_connection(app):
mysql = MySQL()
mysql.init_app(app)
return mysql.get_db().cursor()
Which I call once from my __init__.py before I do any routing, this gets me the db cursor. Then I just freely use that variable within the routing.
I'm not sure how well this will scale up, and frankly I think the flask tutorials are rather poor as the mechanics behind g, app initialisation, routing, current_app and the rest are poorly explained. But what I've got works for now at least. I hope someone else can provide a better answer to this question.
Just encountered the same issue and found this interesting article here.
It's about to use plain SQLAlchemy and to avoid Flask-SQLAlchemy.
Instead of initializing the connection with
db=SQLAlchemy(app)
(which causes the issue in question) I followed the article and did it like this:
class Query:
def __init__(self):
SQLALCHEMY_DATABASE_URI = 'sqlite:///db/mydb.db'
engine = create_engine(SQLALCHEMY_DATABASE_URI, connect_args={'check_same_thread': False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
self.session = SessionLocal()
Base = automap_base()
Base.prepare(engine, reflect=True)
self.Node = Base.classes.mytable
def getTop20(self):
nodes = self.session.query(self.Node).order_by(desc(self.Node.date_modified)).limit(20)
return nodes

Flask-SQLAlchemy application context error

I am trying to use SQLAlchemy not in a view function (I was doing something like this with Flask-APSheduler).
I know that there were already a lot of topics related to this theme, but none of them were helpful to me.
So, first of all I will show my code:
./run.py
from app import create_app
from flask_config import DevConfig, ProdConfig
app = create_app(DevConfig)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
./app/__init__.py
from flask import Flask
from .node import node
from .models import db
def create_app(app_config=None):
app = Flask(__name__, instance_relative_config=False)
app.config.from_object(app_config)
db.init_app(app)
app.register_blueprint(node)
return app
./app/models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Users(BaseFuncs, db.Model):
...
./app/node.py
from flask import Blueprint, request
from .bot import bot, secret
import telebot
node = Blueprint('node', __name__)
#node.route('/{}'.format(secret), methods=['POST'])
def handler():
bot.process_new_updates([telebot.types.Update.de_json(request.get_data().decode('utf-8'))])
return 'ok', 200
./app/bot.py
from flask import current_app as app
...
#bot.message_handler(commands=['test'])
def cmd_test(message):
with app.app_context():
print(Users.query.filter_by(id=0).first())
So when I am trying to call cmd_test from my application I am getting this error:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
I tried to use g variable and before_request methods, because every time before calling the database there is a call to the route 'handler', but this also doesn't work.
I also tried to use db.get_app(), but there was no effect.
So my question is how to call database right outside the views?

Flask-SQLAlchemy: Can't reconnect until invalid transaction is rolled back

So I am using Amazon Web Services RDS to run a MySQL server and using Python's Flask framework to run the application server and Flask-SQLAlchemy to interface with the RDS.
My app config.py
SQLALCHEMY_DATABASE_URI = '<RDS Host>'
SQLALCHEMY_POOL_RECYCLE = 60
My __ init __.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
application = Flask(__name__)
application.config.from_object('config')
db = SQLAlchemy(application)
I have my main application.py
from flask import Flask
from application import db
import flask.ext.restless
from application.models import Person
application = Flask(__name__)
application.debug=True
db.init_app(application)
#application.route('/')
def index():
return "Hello, World!"
manager = flask.ext.restless.APIManager(application, flask_sqlalchemy_db=db)
manager.create_api(Person, methods=['GET','POST', 'DELETE'])
if __name__ == '__main__':
application.run(host='0.0.0.0')
The models.py
class Person(db.Model):
__bind_key__= 'people'
id = db.Column(db.Integer, primary_key=True)
firstName = db.Column(db.String(80))
lastName = db.Column(db.String(80))
email = db.Column(db.String(80))
def __init__(self, firstName=None, lastName=None, email=None):
self.firstName = firstName
self.lastName = lastName
self.email = email
I then have a script to populate the database for testing purposes after db creation and app start:
from application import db
from application.models import Person
person = Person('Bob', 'Jones', 'bob#website.net')
db.session.add(person)
db.session.commit()
Once I've reset the database with db.drop_all() and db.create_all() I start the application.py and then the script to populate the database.
The server will respond with correct JSON but if I come back and check it hours later, I get the error that I need to rollback or sometimes the 2006 error that the MySQL server has gone away.
People suggested that I change timeout settings on the MySQL server but that hasn't fixed anything. Here are my settings:
innodb_lock_wait_timeout = 3000
max_allowed_packet = 65536
net_write_timeout = 300
wait_timeout = 300
Then when I look at the RDS monitor, it shows the MySQL server kept the connection open for quite a while until the timeout. Now correct me if I'm wrong but isn't the connection supposed to be closed after it's finished? It seems that the application server keeps making sure that the database connection exists and then when the MySQL server times out, Flask/Flask-SQLAlchemy throws an error and brings down the app server with it.
Any suggestions are appreciated, thanks!
I think what did it was adding
db.init_app(application)
in application.py, haven't had the error since.
Everytime checking rollback or not is troublesome..
I made insert, update functions which need commit.
#app.teardown_request
def session_clear(exception=None):
Session.remove()
if exception and Session.is_active:
Session.rollback()
It seems not to be a problem with the transactions at the first place, but this is probably caused by an MySQL Error like Connection reset by peer beforehand. That means your connection is lost, probably because your application context was not setup correctly.
In general it is preferrable to use the factory pattern to create your app. This has a lot of advantages, your code is
easier to read and setup
easier to test
avoid circular imports
To prevent the invalid transaction error (that is probably caused by an OperationalError: Connection reset by peer) you should ensure that you are handling the database connection right.
The following example is based on this article which gives a nice explanation of the flask application context and how to use it with database connections or any other extensions.
application.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
def create_app():
"""Construct the core application."""
application = Flask(__name__)
application.config.from_object('config') # Set globals
db = SQLAlchemy()
with application.app_context():
# Initialize globals/extensions in app context
db.init_app(app)
# import routes here
from . import routes
return application
if __name__ == "__main__":
app = create_app()
app.run(host="0.0.0.0")
routes.py
from flask import current_app as application
#application.route('/', methods=['GET'])
def index():
return "Hello, World!"
If you still run into disconnect-problems you should also check the SQLAlchemy documentation on dealing with disconnects and have a look at this question.
Here you missing pool recycle as MySql closes session after some time so you need to add pool recycle so that connections in pool get reconnect after pool recycle time.
app.config['SQLALCHEMY_POOL_RECYCLE'] = 3600
This error usually appears when you create sqlalchemy the engine as a singleton. In that case after the connection is invalidated (in my case it was 3600sec) you get the InvalidTransaction error.
Best advice would be to initialise the db session at the time of application initialisation
db.init_app(app)
and import this db session when ever you have to do some CRUD operation.
Never faced this issue post this change on my application.
Alternatively, use this at the end of the script that populates your database:
db.session.close()
That should prevent those annoying "MySQL server has gone away" errors.

Flask nosetests - unable to connect to test db

I'm making a simple Flask web application for fun and I wanted to use nosetests. I'm stuck at how to use Flask-SQLAlchemy to connect to an in-memory test database in my tests file. When I run my tests - Flask connects to my main app's database and what is more, fails to clean it up after each test. Here's my tests code:
import nose
from nose.tools import *
from pyquery import PyQuery as pq
from flask.ext.sqlalchemy import SQLAlchemy
from app import site, db
from app.models import Post
class TestApp(object):
def setUp(self):
site.config['TESTING'] = True
site.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
self.test_app = site.test_client()
db.create_all()
def tearDown(self):
# db.session.remove()
db.drop_all()
def test_posts_index(self):
db.session.add(Post('title', 'body'))
db.session.add(Post('title2', 'body'))
db.session.commit() # this writes to production db ie app.db file
# instead of sqlite://
rv = self.test_app.get('/posts')
d = pq(rv.data)
print len(d('h1'))
assert len(d('h1')) == 2
And here's my app/__init__.py code:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from app import config
site = Flask(__name__)
site.config['SQLALCHEMY_DATABASE_URI'] = config.db_uri
db = SQLAlchemy(site)
site.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
from app import db_setup
db_setup.create_db()
import controllers, models
The db_setup.create_db() in app/__init__.py function looks simply like this:
from app import db
from app.models import Post
def create_db():
db.create_all()
db.session.commit()
I tried instantiating the application and database in the tests file, but then my models don't work because they from app import db, where db is the production db object. I also sprinkled a few print statements in the test case like print db and they print out something like <SQLAlchemy engine sqlite://>, but it still writes to the production db anyways.
I'd really appreciate any tips on how to get around this. Thanks!
Why don't you use something about the environment to determine whether the app starts in a testing or live mode?
if 'testing' in os.environ:
site.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
else:
site.config['SQLALCHEMY_DATABASE_URI'] = config.db_uri
There are so many ways to skin this particular cat. If you don't like the idea of having if blocks littering your code you can import your settings from an entirely separate module based on whether the app is started in testing or live mode.
I was able to figure out the problem, it's related to me initiating a connection to the database in my __init__.py file, which I shouldn't do.
The culprit was the
from app import db_setup
db_setup.create_db()
code. Essentially, every time I did an from app import db, I think that app gets instantiated, it calls db_setup.create_db(), which creates the tables using the production config. From there on, despite trying to set the flask app config SQLALCHEMY_DATABASE_URI to an in memory database, the db object would continue to use the database instantiated in the __init__.py file.
All I have to do is call create_all() from the environment my will run in at that time. Hope this helps anyone how might run into something similar.
I had the same problem, but I didn't use a db.create_all() type statement in my init.py file at all.
In the end, the only way I could around the issue was to use
def setUp(self):
with app.app_context():
db.create_all()

When scattering Flask Models, RuntimeError: 'application not registered on db' was raised

I am re-factoring my Flask application by scattering the models, blueprints but I am having a runtime error.
def create_app():
app = flask.Flask("app")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.register_blueprint(api)
db.init_app(app)
db.create_all()
return app
I have the following problem(the sample project are hosted here: https://github.com/chfw/sample):
Traceback (most recent call last):
File "application.py", line 17, in <module>
app = create_app()
File "application.py", line 12, in create_app
db.create_all()
File "\AppData\Roaming\Python\Python27\site-packages\flask_sqlalchemy\__init__.py", line 856, in create_all
self._execute_for_all_tables(app, bind, 'create_all')
File "\AppData\Roaming\Python\Python27\site-packages\flask_sqlalchemy\__init__.py", line 836, in _execute_for_all_tables
app = self.get_app(app)
File "\AppData\Roaming\Python\Python27\site-packages\flask_sqlalchemy\__init__.py", line 809, in get_app
raise RuntimeError('application not registered on db
'RuntimeError: application not registered on db
instance and no application bound to current context
I did a research on this topic. The re-factoring is suggested here:
Flask-SQLAlchemy import/context issue
The same problem was raised here:
http://flask.pocoo.org/mailinglist/archive/2010/8/30/sqlalchemy-init-app-problem/#b1c3beb68573efef4d6e571ebc68fa0b
And the above thread(2010) suggested a hack like this:
app.register_blueprint(api)
db.app=app #<------------<<
db.init_app(app)
Did anyone know how to do this properly? How did you solve it?
Thanks
This has to do with Flask's application context. When initialized with db.init_app(app), Flask-SQLAlchemy doesn't know which app is the "current" app (remember, Flask allows for multiple apps in the same interpreter). You could have multiple apps using the same SQLAlchemy instance in the same process, and Flask-SQLAlchemy would need to know which is the "current" one (due to Flask's context local nature of everything).
If you need to do this during runtime, you must explicitly say which app is the "current" app for all calls. You can do this by changing your code to use a with app.app_context() block:
def create_app():
app = flask.Flask("app")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.register_blueprint(api)
db.init_app(app)
with app.app_context():
# Extensions like Flask-SQLAlchemy now know what the "current" app
# is while within this block. Therefore, you can now run........
db.create_all()
return app
If you are writing a standalone script that needs the app context, you can push the context at the beginning rather than putting everything in a with block.
create_app().app_context().push()
If you write a command for Flask's cli the command will automatically have access to the context.
Mark's answer was great and it helped me a lot. However, another way to approach this is to run the code that relies on the app context in a function decorated with #app.before_first_request. See http://flask.pocoo.org/docs/0.10/appcontext/ for more information. That's in fact how I ended up doing it, largely because I wanted to be able to call the initialization code outside of flask as well, which I handle this way.
In my case I want to be able to test SQLAlchemy models as plain SQLAlchemy models without Flask-SQLAlchemy, though the db in the code below is simply a (Flask) SQLAlchemy db.
#app.before_first_request
def recreate_test_databases(engine = None, session = None):
if engine == None:
engine = db.engine
if session == None:
session = db.session
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
# Additional setup code

Categories

Resources