7

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?

0

1 Answer 1

6

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:

  • You are using Flask-Migrate to manage the schema, so don't call 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.
  • If you are configuring your database from environment variables, then don't use a variable config object. It's easier to work with a Flask app factory if you just import 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.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.