flask-peewee icon indicating copy to clipboard operation
flask-peewee copied to clipboard

Support deferring configuration of database to after initialization

Open xiaq opened this issue 12 years ago • 16 comments

Peewee allows you to do this, by passing None to __init__:

from peewee import *
db = SqliteDatabase(None)
db.init('a.db')

Flask-SQLAlchemy too, by leaving out the app argument:

db = SQLAlchemy()
db.init_app(app)

This is important if you need to create the app in a function instead of in the module global, which makes unit testing easier and is better practice. The complete form for the above example is:

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    return app

Deferring configuration of database still allows you to put db in the module global and use db.Model as base class for ORM models.

xiaq avatar Apr 25 '13 16:04 xiaq

I found out the author is precisely that of Peewee, so I needn't have explained this much. :-)

I also identified a problem. A deferred Peewee database still needs to have its type (sqlite/postgre/...) specified. Seems Peewee needs to be enhanced so that a deferred peewee.Database can has its type unknown.

xiaq avatar Apr 25 '13 17:04 xiaq

+1. My unit tests run twice as long because they are making a connection to the MySQL database for every simulated request. Using a config that points to an in-memory SQLite database doubles the speed, but it would probably still be much faster if it could be told not to connect until later. Here's the MySQL query log created by running unit tests:

Time                 Id Command    Argument
130728 21:45:13   146 Connect   schdl@localhost on schdl
          146 Query set autocommit=0
          146 Quit  
          147 Connect   schdl@localhost on schdl
          147 Query set autocommit=0
          147 Quit  
          148 Connect   schdl@localhost on schdl
          148 Query set autocommit=0
          148 Quit  
          149 Connect   schdl@localhost on schdl
          149 Query set autocommit=0
          149 Quit  
130728 21:45:14   150 Connect   schdl@localhost on schdl
          150 Query set autocommit=0
          150 Quit  
          151 Connect   schdl@localhost on schdl
          151 Query set autocommit=0
          151 Quit  
          152 Connect   schdl@localhost on schdl
          152 Query set autocommit=0
          152 Quit  
          153 Connect   schdl@localhost on schdl
          153 Query set autocommit=0
          153 Quit  
          154 Connect   schdl@localhost on schdl
          154 Query set autocommit=0
          154 Quit  
          155 Connect   schdl@localhost on schdl
          155 Query set autocommit=0
          155 Quit  
          156 Connect   schdl@localhost on schdl
          156 Query set autocommit=0
          156 Quit  
          157 Connect   schdl@localhost on schdl
          157 Query set autocommit=0
          157 Quit  
          158 Connect   schdl@localhost on schdl
          158 Query set autocommit=0
          158 Quit  
          159 Connect   schdl@localhost on schdl
          159 Query set autocommit=0
          159 Quit

emosenkis avatar Jul 29 '13 01:07 emosenkis

What is the status of this feature? Are there any forks which implemented this? Is there a reason why flask-peewee handles is differently?

dmr avatar Aug 21 '13 04:08 dmr

I need to fix this. Will try and get things cleaned up in the next day or two.

coleifer avatar Oct 07 '13 16:10 coleifer

+1. What's the workaround that people are doing when they use application factories? global instance?

mangrish avatar Nov 11 '13 05:11 mangrish

@mangrish Build a proxy class. A bit clumsy, though.

xiaq avatar Nov 11 '13 16:11 xiaq

@coleifer Seems this has been implemented with f387f73? It has been some time since I used flask-peewee so I'm not quite sure - have been using raw peewee lately...

It would be nice if the deferred configuration of engine is implemented in the peewee proper though :)

xiaq avatar Nov 11 '13 16:11 xiaq

Ah... not quite there yet. You still need to specify the app when initializing the database:

import flask, flask_peewee
app = flask.Flask(__name__)
db = flask_peewee.Database(app)

better make it possible to write this, a la Flask-SQLAlchemy:

import flask, flask_peewee

db = flask_peewee.Database()

def create_app():
    app = flask.Flask(__name__)
    db.init_app(app)
    return db

I'll probably send a PR tonight (UTC+8 here :)...

xiaq avatar Nov 11 '13 16:11 xiaq

Ah, harder than I though...

flask_peewee.Database needs to have its Model determined before init_app is called. Which is not possible without determining the database engine...

So better implement deferred engine selection in peewee... :)

xiaq avatar Nov 11 '13 16:11 xiaq

Crossposting: I think that this issue is related to https://github.com/coleifer/flask-peewee/issues/103

It would be great if we could find a way to do configuration independent of a given app, and independent of the usage of flask at all (i.e., only use the peewee models in a separate non-web app).

EDIT: I just realized that github does the pingback automagically for you, thus sorry for the noise.

arnuschky avatar Nov 11 '13 17:11 arnuschky

Anyone found a better, "clean" solution yet?

lmas avatar Jan 10 '14 17:01 lmas

No. Would be interested in this as well, same use-case as mentioned by @xiaq this time.

Not sure if this can be solved without substantial changes in peewee.

arnuschky avatar Jun 01 '14 00:06 arnuschky

So, I found a (hacky) solution for this. In my app, I use a init_app method as proposed by @xiaq. All my models extend peewee.Model. The code below, when placed in the init_app method, will scan the package for all classes that extend peewee.Model. If it finds such a class, it will monkey-patch the newly created database object into the Model's meta info.

    for _, name, _ in pkgutil.iter_modules(package_path):
        m = importlib.import_module('%s.%s' % (package_name, name))
        for item in dir(m):
            item = getattr(m, item)
            if item.__class__ == Model.__class__:
                item._meta.database = self.database

Code needs of the package path and name, eg., the package with all your models.

Not pretty, but it works. This allows us also to get around get_model_class and some other things from Database()

arnuschky avatar Jun 01 '14 01:06 arnuschky

Have you guys tried using the peewee Proxy object? That will probably work. Maybe I will fiinally fix this...

coleifer avatar Jun 01 '14 13:06 coleifer

Works like a charm. Much nicer than my hack (but I was soo proud! ;) ) Thanks a bunch @coleifer

arnuschky avatar Jun 01 '14 14:06 arnuschky

Is there an example somewhere on how to use the Proxy object with flask-peewee? flask-peewee checks to see if the engine is a subclass of Database, which Proxy is not. It also requires a name parameter which it will attempt to pass to Proxy during initialization. Do I need to override flask-peewee 's Database class?

kolanos avatar Aug 11 '14 22:08 kolanos