I'm not able to create tables into a database (PostgresSQL) when using Flask's app factory pattern.
I've looked at different examples from Stackoverflow and the Flask-SQLAlchemy source code. My understanding is that with the factory app pattern, I need to set up the so-called context before I can try creating the tables. However, when I create the context, Flask app's config dictionary gets reset and it doesn't propagate the configurations forward.
Here's my model.py
import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class MyModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
some = db.Column(db.DateTime, nullable=False)
random = db.Column(db.String, nullable=False)
model = db.Column(db.String, nullable=False)
fields = db.Column(db.Integer, nullable=False)
def __init__(self, some: datetime.datetime, random: str, model: str,
fields: int) -> None:
self.some = some
self.random = random
self.model = model
self.fields = fields
def __repr__(self):
return f"""<MyModel(some={self.some}, random={self.random},
model={self.model}, fields={self.fields})>"""
Here's the app's __init__.py
file
import os
from flask import Flask
from flask_migrate import Migrate
from myapp.models import db
migrate = Migrate()
def create_app(config):
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
migrate.init_app(app, db)
from .models import MyModel
with app.app_context():
db.create_all()
@app.route('/hello')
def hello():
return('Hello World!')
return app
I also have a main.py
file:
from myapp import create_app
from config import Config
app = create_app(Config)
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8080, debug=True)
The root folder contains the main.py
and the MyModule
app folder. Furthermore, I've set up a Postgres instance and the required config constants in a config.py
file:
import os
from dotenv import load_dotenv
basedir = os.path.abspath(__file__)
load_dotenv(os.path.join(basedir, '.env'))
class Config(object):
DEBUG = False
TESTING = False
CSRF_ENABLED = True
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
SQLALCHEMY_TRACK_MODIFICATIONS = False
I'm reading the variables from an .env file.
When I run main.py
, I get the following error:
(venv) $:project-name username$ python main.py
Traceback (most recent call last):
File "main.py", line 4, in <module>
app = create_app(Config)
File "/Users/username/project-name/myapp/__init__.py", line 18, in create_app
db.create_all()
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 1033, in create_all
self._execute_for_all_tables(app, bind, 'create_all')
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 1025, in _execute_for_all_tables
op(bind=self.get_engine(app, bind), **extra)
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 956, in get_engine
return connector.get_engine()
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 560, in get_engine
options = self.get_options(sa_url, echo)
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 575, in get_options
self._sa.apply_driver_hacks(self._app, sa_url, options)
File "/Users/username/venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 877, in apply_driver_hacks
if sa_url.drivername.startswith('mysql'):
AttributeError: 'NoneType' object has no attribute 'drivername'
Now what's strange here is that when I print print(app.config)
inside the create_app function, the configurations are in place, just like I want them to be. So for example SQLALCHEMY_DATABASE_URI=postgresql://testuser:testpassword@localhost:5432/testdb'
. However, when I print the same info inside the app.app_context() loop, SQLALCHEMY_DATABASE_URI=None
(as an example, the other key-value pairs are also reset).
What am I missing here?
You are loading your .env
file incorrectly:
basedir = os.path.abspath(__file__)
load_dotenv(os.path.join(basedir, '.env'))
basedir
is the module file itself, not the directory. So for a file named config.py
, basedir
is set to /..absolutepath../config.py
, not /..absolutepath../config.py
. As a result, you are asking dotenv()
to load the file /..absolutepath../config.py/.env
, which won't exist.
You are missing a os.path.dirname()
call:
basedir = os.dirname(os.path.abspath(__file__))
You can avoid this issue altogether by using dotenv.find_dotenv()
:
load_dotenv(find_dotenv())
which uses the __file__
attribute of the module it is called from.
Other remarks:
db.create_all()
. Instead use the Flask CLI and the Flask Migrate commands (flask db init
, flask db migrate
, and flask db upgrade
to create a migration directory and your initial migration script and then upgrade your connected database to use the latest schema version.config
from the current project for default configuration that applies to all development. You can always load in more configuration via app.config.from_obj()
or app.config.from_envvar()
, using optional arguments and an environment variable, as needed.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With