Accessing a database created with flask-sqlalchemy from a separate flask app - python

+-- 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.

Related

Python: unable to import classes from higher directory

I am learning flask. I have a directory structure that looks something like this.
project
- controllers
-- auth.py
-- main.py
- db
-- setup.py
- models
-- models.py
- templates
-- base.html
-- index.html
-- login.html
-- signup.html
-- 404.html
-__init__.py
The file init.py looks like this
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# init SQLAlchemy so we can use it later in our models
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = '9OLWxND4o83j4K4iuopO'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:#localhost/mission_to_mars'
db.init_app(app)
# blueprint for auth routes in our app
from .controllers.auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .controllers.main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
I'm trying to import the db variable and create_app() function in controllers/auth.py, controllers/main.py, models/models.py, db/setup.py
I have tried the below syntaxes
from .. import db, create_app
which gives the error:
ImportError: attempted relative import with no known parent package
I have also tried
from project import db, create_app
which gives the error
ModuleNotFoundError: No module named 'project'
I want to know how I can import classes from different directories so that I can use them further in the project.
Thanks in advance

Export variable from app.py to another python file

I created a Flask app with SQLAlchemy.
I initialised the database in app.py but I want to use it in another file: otherfile.py.
app.py
app = Flask(__name__)
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///my_database'
db = SQLAlchemy(app)
print("TEST")
otherfile.py
from app import db
print(db)
But I get the error.
ImportError: cannot import name 'db'
Basically, I am doing this because I do not want to write the SQLAlchemy logic in app.py but I want to keep it in another file.
In addition, I just want the variable db to be exported. I do not want that when I run otherfile.py, this runs also print("TEST") which is in app.py
I looked at these answer with little luck:
How to share the global app object in flask?
Split Python Flask app into multiple files
How to divide flask app into multiple py files?
The SQLAlchemy class doesn't need to receive the app param on its initialization. So what you can do is create the db object in your otherfile.py and import it in app.py, where you can pass it the app object with SQLAlchemy.init_app().
app.py
from flask import Flask
from otherfile import db
app = Flask(__name__)
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///my_database'
db.init_app(app)
otherfile.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

Using Flask-migrate for models in multiple files

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

AssertionError in Flask App when connecting to database using config in blueprint

I am building a Flask application which connects to an existing MySQL database as an exercise to learn Flask. I am encountering an error when trying to connect to the database, from a database object which is instantiated from a blueprint.
My Project structure is as follows
project
├─── instance
├─── config.py
├─── application
├─── _src
├─── db.py
├─── extensions.py
├─── admin
├─── templates
├─── __init__.py
├─── views.py
├─── static
__init__.py
My __init__.py (in the applications directory) has the following code:
from flask import Flask
# Config file
app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile("config.py")
# Blueprints
from application.admin.views import adminBlueprint
# Register the blueprint
app.register_blueprint(adminBlueprint)
My Config file has the following:
#####################
# Database details ##
#####################
DB_USERNAME = "username"
DB_PASSWORD = "password"
DB_DATABASE_NAME = "databasename"
DB_HOST = "localhost"
The views file, in my admin folder has the following:
# Imports
from flask import render_template, Blueprint
from .._src.db import DB
from .._src import admin as admin
# Config
adminBlueprint = Blueprint("admin", __name__, template_folder="templates")
# Routes
#adminBlueprint.route("/admin")
def admin():
# Connect to the database
db = DB()
cursor, conn = db.connectDB()
# Get the required data
projects = admin.getProjects(cursor, "all")
# Close the database connection
db.close()
# Render data to the template
return render_template("admin.html", projects=projects)
My extensions file, in the _src folder, which is used to allow access to the MySQL object from the blueprint, has the following code:
from flaskext.mysql import MySQL
mysql = MySQL()
And my db file in the _src directory has the following:
from flask import current_app
from .._src.extensions import mysql
class DB:
def __init__(self):
# Something will be done here in the future
pass
def connectDB(self):
# Provide the database connection details
current_app.config['MYSQL_DATABASE_USER'] = current_app.config["DB_USERNAME"]
current_app.config['MYSQL_DATABASE_PASSWORD'] = current_app.config["DB_PASSWORD"]
current_app.config['MYSQL_DATABASE_DB'] = current_app.config["DB_DATABASE_NAME"]
current_app.config['MYSQL_DATABASE_HOST'] = current_app.config["DB_HOST"]
mysql.init_app(current_app)
# Connect to the database
try:
self.conn = mysql.connect()
cursor = self.conn.cursor()
# Return the cursor object
return cursor, self.conn
except:
return False
def close(self):
self.conn.close()
I'm getting the following 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.
And the debugger is pointing to this file in the db file:
mysql.init_app(current_app)
I'm a little out of my depth and I don't really understand what the problem is. Can I only initialize the MySQL object from the same place that I initialize the Flask app? If so, how can I then access the MySQL object from the blueprint?
Any help is appreciated.
I was able to fix this by putting my app instantiation into my extensions file instead of my init file:
extensions.py
from flask import Flask
app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile("config.py")
Then, in my database connection object, I can import the current app to get the settings:
from flask import current_app
from .._src.extensions import mysql
I also removed this line, as I am now achieving this in my extensions file:
mysql.init_app(current_app)

Import statement flow

My app layout
my_app
__init__.py
my_app
__init__.py
startup
__init__.py
create_app.py
create_users.py
common_settings.py
core
__init__.py
models.py
views.py
errors
__init__.py
errors.py
Inner __init__.py
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) # The WSGI compliant web application object
db = SQLAlchemy(app) # Setup Flask-SQLAlchemy
manager = Manager(app) # Setup Flask-Script
from my_app.startup.create_app import create_app
create_app()
create_app.py
def create_app(extra_config_settings={}):
# Load all blueprints with their manager commands, models and views
from my_app import core
return app
core/__init__.py
# from . import views
views.py
from my_app import app, db
from flask import Flask, request
#app.errorhandler(Error)
def handle_invalid_usage(error):
response = jsonify(data=error.to_dict())
response.status_code = error.status_code
return response
I based this code on a tutorial I found. Everything works fine as long as I leave the __init__.py in the core folder empty.
When I don't, I get a NameError: name Error is not defined in my views.py. Error comes from errors.py.
I have three questions:
1) Why does this happen only when I leave the import statement in core/__init__.py.
2)
create_app.py
app.config.from_envvar('ENV_SETTINGS_FILE')
# Other app.config commands here
from my_app import core
return app
What happens when from my_app import core runs?
3) Finally when I return app, is this to ensure that Inner __init__.py file contains the updated app object?
Any explanations would be greatly appreciated!
Trying to build and configure an app with dynamic imports is really bad news and confusing for the reasons you are discovering. A much better and understandable pattern would be a more typical factory:
def create_app():
app = Flask(__name__)
configure_app(app, config)
register_db(app)
add_views(app)
add_manager(app)
return app
if __name__ == '__main__':
app = create_app()
app.run()
But since you're asking, your problem is here:
from my_app import app, db
from flask import Flask, request
#app.errorhandler(Error) # Error is not imported
def handle_invalid_usage(error):
response = jsonify(data=error.to_dict())
response.status_code = error.status_code
return response
The error occurs because views.py is imported, the code compiler comes across Error and cannot find a reference to it.
For your second question: from my_app import core causes core.__init.__ to run, which (presumably) adds the views onto the app object.

Categories

Resources