How to get a handle to the initialized database from a Flask application with SQLAlchemy? - flask

I would like to add data to a database with Flask-SQLAlchemy without the Flask app running.
Is there a way to get db back from the app after the app and the database have been initialized.
My code looks like
db = SQLAlchemy()
def init_app():
app = Flask(__name__)
db = SQLAlchemy(app)
db.init_app(app)
return app
And what I would like to do is something like
from app import init_app
app = init_app() # initialized but not running
# db is used in model.py, but not initialized
# with Flask
# from db = SQLAlchemy()
from model import Machine # class Machine(db.Model)
p = Machine(name='something')
# now I need the initialized db from somewhere
db.session.add(p)
db.session.commit()
Basically I would like to do what's described here:
Another disadvantage is that Flask-SQLAlchemy makes using the database
outside of a Flask context difficult. This is because, with
FLask-SQLAlchemy, the database connection, models, and app are all
located within the app.py file. Having models within the app file, we
have limited ability to interact with the database outside of the app.
This makes loading data outside of your app difficult. Additionally,
this makes it hard to retrieve data outside of the Flask context.

Well, once you initialize the app, Flask spines a server (either development or production, whichever you set), so if you would like to add data to a database with Flask-SQLAlchemy without the Flask app running, you would better use the flask shell command which runs in the context of the current app, then you could add your data.
But first, it would be better is you set up your app as the following so we could directly import stuff like db, auth, etc:
...
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def init_app():
app = Flask(__name__)
db.init_app(app)
return app
In the root of your project, type in the terminal the following command
flask shell
Now that you have a shell running in the context of the current app but not the server not running:
from app import db
from model import Machine # class Machine(db.Model)
p = Machine(name='something')
# now I need the initialized db from somewhere
db.session.add(p)
db.session.commit()

From the wonderful tutorial...
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-database
Define something like this...
from app import app, db
from app.models import User, Post
#app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User, 'Post': Post}
And then...
(venv) $ flask shell
>>> db
<SQLAlchemy engine=sqlite:////Users/migu7781/Documents/dev/flask/microblog2/app.db>
>>> User
<class 'app.models.User'>
>>> Post
<class 'app.models.Post'>

Related

Flask-sqlalchemy 3.0.2 creating a new DB inside a Flask endpoint

I'm currently migrating my Flask app from flask-sqlalchemy 2.5.1 to 3.0.2. The app includes an endpoint that lets the client create a new database file at a selected path. Before the migration, it was achieved by simply setting the path and creating the tables in the following way:
app.config['SQLALCHEMY_DATABASE_URI'] = filepath
db.create_all()
However, the file is no longer automatically created in 3.0.2.
Question
I have been digging through the flask-sqlalchemy and sqlalchemy documentations for the past two days yet I can't find anything that would mention the changed behaviour. Creating the file before setting the config doesn't work either, as the tables are not created and the file size is 0b after calling the endpoint.
How can I create a new sqlalchemy DB file inside of a Flask endpoint?
Below is a minimal reproducible example:
server.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
TEST_DB_PATH = os.path.join(os.getcwd(), "test.db")
# create the extension
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
# create the app
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
# configure the first SQLite database, in memory
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///"
# initialize the app with the extension
db.init_app(app)
with app.app_context():
db.create_all()
#app.route('/test')
def test():
# configure the second SQLite database, relative to the app instance folder
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + TEST_DB_PATH
db.create_all()
return "OK", 200
if __name__ == '__main__':
app.run()
test_server.py
import server
import os
import pytest
#pytest.fixture(scope="session")
def client():
app = server.app
return app.test_client()
def test_create_db_file(client):
# clean local test.db file if it exists
try:
os.remove(server.TEST_DB_PATH)
except OSError:
pass
ans = client.get("test")
assert ans.status_code == 200, ans.data
# this assertion fails
assert os.path.exists(
server.TEST_DB_PATH), f"DB file created at execution cannot be found at " + server.TEST_DB_PATH
assert os.path.getsize(
server.TEST_DB_PATH) > 0, "Created db file size is 0"
Running the test
py -m pytest .\test_server.py
The assertions fail with flask-sqlalchemy 3.0.2 and pass with 2.5.1.
Environment
Windows 11 x64
flask-sqlalchemy 2.2.2
sqlalchemy 1.4.46
pytest 7.2.0
python 3.9.13

flask init_db in app_context vs not? what is the difference?

I have multiple apps and use flask_sqlalchemy with the style below:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
However, in the test, all the app linked to the same database.
after I switch to the following style. Every app can linked to its own database.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
with app.app_context():
db.init_app(app)
return app
I did not understand why? If the second case is better, why it is not in the official doc?
credit: I copied the second style from https://hackingandslacking.com/demystifying-flasks-application-context-c7bd31a53817
Each app you create has it is own context, using the below block of code:
with app.app_context():
db.init_app(app)
means that you initialize those instances to that app context only and trying to access those instances somewhere else will result in the below error.
RunTimeError: Working outside of application context.
You can read more about Flask app context here

How can I initialize the database automatically with SQLalchemy and Alembic?

Currently, I run
$ flask db init
$ flask db migrate -m "initialization"
$ flask db upgrade
if the database does not exist. I would like to run this within Python, e.g. something like
app.create_db()
so that I don't have to care about setting the database up. Is that possible?
I use the flask-sqlalchemy and flask-migrations plugins
You can use SQLAlchemy-Utils for this.
from sqlalchemy import create_engine
from sqlalchemy_utils import database_exits,create_database
def validate_database():
engine = create_engine('postgres://postgres#localhost/name')
if not database_exists(engine.url): # Checks for the first time
create_database(engine.url) # Create new DB
print("New Database Created"+database_exists(engine.url)) # Verifies if database is there or not.
else:
print("Database Already Exists")
call this method in your __init__.py file so that it checks every time your server starts.
Obviously, you have installed flask-migrate, flask-sqlalchemy.
So, you can do like this:
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
db.create_all()
API DOC: flask.ext.sqlalchemy.SQLAlchemy.create_all
but your Question has confused me. why restricted by SQLAlchemy and Alembic?
There's db.create_all() but I think that when you're using migrations you should stick to migration scripts.
Something to note is that if you have your migration files all set up (i.e migration folder) then all you need is flask db migrate
If you're running this locally, I would stick to doing this command manually.
If you're using this on a server, you should probably use a deployment script that does this for you. You can look at fabric (www.fabfile.org) for information on how to run terminal commands

using flask-migrate with flask-script and application factory

I'm building flask application and decided to try application factory approach this time, but got into trouble with flask-migrate and can't figure out simple solution.
Please note that I want to pass config location as an option to the script
manage.py:
manager = Manager(create_app)
manager.add_option("-c", "--config", dest="config_module", required=False)
then i need to create migrate instance and add command to the manager:
with manager.app.app_context():
migrate = Migrate(current_app, db)
manager.add_command('db', MigrateCommand)
but app instance is not created yet, so it fails
I know I can pass config in environment variable and create application before creating manager instance, but how to do it using manager options?
When you use a --config option you have to use an application factory function as you know, as this function gets the config as an argument and that allows you to create the app with the right configuration.
Since you have an app factory, you have to use the deferred initialization for all your extensions. You instantiate your extensions without passing any arguments, and then in your app factory function after you create the application, you call init_app on all your extensions, passing the app and db now that you have them.
The MigrateCommand is completely separate from this, you can add that to the Manager without having app and db instances created.
Example:
manage.py:
from app import create_app
from flask_migrate import MigrateCommand, Manager
manager = Manager(create_app)
manager.add_option("-c", "--config", dest="config_module", required=False)
manager.add_command('db', MigrateCommand)
app.py (or however your app factory module is called)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app(config):
app = Flask(__name__)
# apply configuration
# ...
# initialize extensions
db.init_app(app)
migrate.init_app(app, db)
return app

Importing Flask app when using app factory and flask script

This is Flask app context
app = Flask(__name__)
with app.app_context():
# insert code here
Most of the use cases of app context involves having 'app' initialized on the same script or importing app from the base.
My application is structured as the following:
# application/__init__.py
def create_app(config):
app = Flask(__name__)
return app
# manage.py
from application import create_app
from flask_script import Manager
manager = Manager(create_app)
manager.add_command("debug", Server(host='0.0.0.0', port=7777))
This might be really trivial issue, but how I should call 'with app.app_context()' if my application is structured like this?
Flask-Script calls everything inside the test context, so you can use current_app and other idioms:
The Manager runs the command inside a Flask test context. This means that you can access request-local proxies where appropriate, such as current_app, which may be used by extensions.
http://flask-script.readthedocs.org/en/latest/#accessing-local-proxies
So you don't need to use with app.app_context() with Manager scripts. If you're trying to do something else, then you'd have to create the app first:
from application import create_app
app = create_app()
with app.app_context():
# stuff here