Thanks for your reply Charles :)
Yes, we heard about Flask-Peewee, and use it. And again yes, you
groked it, I'm talking about two different but related problems:
deferring (the db creation) and structuring (the source code).
Note: You may want to just skip to the "TL;DR" section.
To put you in context We are testing and learning Flask, and we
studied some options for the database/ORM layer. Originally we
selected SqlAlchemy and Peewee as test cases, one being complex and
explicit and the other one being simple and implicit; then created a
sample app with each one, and finally decided that Peewee is closer
to our needs :)
This is our story with Peewee in Flask:
First step - Peewee
We created a sample Flask app that just used Peewee (we wanted to
learn how to use Peewee on a standard project, without the
Flask-Peewee 'magic'), being a simple structure much like the one
you propose, but separating the Flask app and Peewee database (
the
code is online if you want to take a look), similar to this:
app.py
-- Flask app initialization.
database.py
-- Peewee database initialization and base model.
user.py
(your models.py) -- imports database.py and then defines the user
model.
cowlab.py
(your main.py + views.py) -- imports app.py, database.py...,
defines the views (controller) and runs the app. (note: 'cowlab'
is the project name)
Why did we split app.py into app.py and database.py? Because if we
want to use a different database on our unit tests, we are forced to
overwrite the application configuration (on the test files) before
loading the database or models (before Database(app) is called):
from app import app # Flask app with default config
app.config.from_object('config.TestingConfig') # Load testing config *before* setting up the database. FIXME: Ugly!
from database import database # 'Database(app)' is called here
from user import User # Import the peewee models, using the testing database.
It works, but it's ugly not having all the imports together at the
begin of the file :(
With the 0.9.6 deferred loading, our code could be improved, and
instead of doing things like:
# ...load the app configuration (set 'app.testing') before reaching this point:
database = peewee.SqliteDatabase('testing.db' if app.testing else 'development.db', threadlocals=True)
We would do:
database = peewee.SqliteDatabase(None,threadlocals=True)
# ...load the app configuration anytime (i.e. 'app.testing' or the database name), and afterwards:
database.init('testing.db' if app.testing else 'development.db')
That would solve the ugliness I was talking about :)
...as long as we don't want to switch database engines for
developing and testing (like Postgres for developing and Sqlite for
testing) :(
Q: Is there a way to defer the selection of the database
engine?
Second step - Flask-Peewee
Afterwards we switched to flask-peewee, to take advantage of its
features (the authentication and the admin views). (
code)
Thanks Flask-Peewee, we wouldn't even really need to define a
'BaseModel' and its Meta inner class to set the database,
Flask-Peewee does it for us nicely:
>>> from flask_peewee.db import Database
>>> from app import app
>>> database = Database(app)
>>> database.database # 'Automagically' setup with the database configured in app.config
<peewee.SqliteDatabase at 0x92ef48c>
>>> database.Model # The base model is also setup with the configured database.
flask_peewee.db.BaseModel
>>> database.Model._meta.database
<peewee.SqliteDatabase at 0x92ef48c>
>>> database.Model._meta.database.database
'development.db'
And (I have just checked) we may use deferred loading too with
Flask-Peewee:
from app import app # Note: app.config['DATABASE'] = {'engine': 'peewee.SqliteDatabase', 'name': None, 'check_same_thread': False }
from flask_peewee.db import Database
db = Database(app)
# ...overwrite the app configuration anytime (set 'app.testing'), and afterwards:
db.database.init('testing.db' if app.testing else 'development.db')
But again, it only allows to defer the database name, not the
database engine :(
Third step - Decoupling
Now, on a third step, we are trying to refactor the code to make the
models independent of the Flask app, so they can be reused (for
example, in a -long and distant- future, we could have a non-flask
backend that needs to access the database). That's why I don't like
having to import the
app.py (or
database.py) on '
models.py'
(or wherever the models are defined), as that makes the models
depend on Flask. I know this is not a problem on small sized
projects, but as we are doing this to learn, we want to check
whether Peewee can be scaled to bigger setups.
So, to allow this decoupling we need a way to allow setting the
database to use *after* the models have been loaded, and without
requiring the models to "
import app".
I think that we can accomplish it by using both the deferred
database feature of 0.9.6 *and* a sort of '
use_db'
(name-it-as-you-like) function.
TL;DR - What I want to do
An example of what I would like to be able to do:
import peewee
class A(peewee.Model):
name = peewee.CharField()
class B(Something):
text = peewee.CharField()
# Currently the database for 'A' and 'B' models is the default 'peewee.db' Sqlite one.
peewee.use(peewee.SqliteDatabase('first.db'))
A.create_table()
peewee.use(peewee.PostgresqlDatabase('second',user='code'))
B.create_table()
# Now we have a Sqlite database named 'first.db' with a table named 'a'
# and a Postgres database with a table named 'b'.
peewee.use(peewee.SqliteDatabase('third.db'), A)
peewee.use(peewee.SqliteDatabase('fourth.db'), B)
A.create_table() # Creates the table in the 'third.db' Sqlite database.
B.create_table() # Creates the table in the 'fourth.db' Sqlite database.
A.create(name="a test") # Inserts a record in the 'a' table of 'third.db'
B.create(name="a test", text="some text") # Inserts a record in the 'b' table of 'fourth.db'
The use function would be something like this:
def use(db, basemodel=peewee.Model):
"""
Allows to set the database to use for the Peewee models.
If basemodel is specified only that model and subclasses are updated.
"""
if basemodel != peewee.Model: # Don't touch peewee.Model
basemodel._meta.database = db
for cls in basemodel.__subclasses__():
cls._meta.database = db
use(db, cls) # Recursive!
What do you think?
Does somebody see some hidden problem in this idea?
Thanks for your thoughts!
El 14/05/12 01:29, Charles escribió: