2011-02-17 20 views
23

Parece práctica común en el frasco para iniciar la siguiente manera:¿Cómo se configura una aplicación Flask con SQLAlchemy para realizar pruebas?

from flask import Flask 
from flaskext.sqlalchemy import SQLAlchemy 
app = Flask(__name__) 
SQLALCHEMY_DATABASE_URI = 'something' 
app.config.from_object(__name__) 
db = SQLAlchemy(app) 

y luego importar y utilizar app y db todas partes. Pero cuando crea db de este modo, toma la configuración de la aplicación, y parece que esta configuración no puede anularse una vez que ocurre. Hay algunas páginas en el sitio web de Flask sobre cómo hacer fábricas de aplicaciones, pero no está claro cómo podría seguir usando app y db en todas partes si lo hiciera.

¿Cómo escribo un script para probar mi aplicación Flask con una base de datos diferente? ¿Cómo debería estructurar mi aplicación para que esto sea posible? ¿Tengo que usar module s?

+3

Mientras tanto, voy a usar app.config.from_envvar y siempre especifico el archivo de configuración en la línea de comandos cuando ejecuto mi aplicación de diferentes maneras. –

+0

que parece una solución, pero si olvida y ejecuta pruebas con el db erróneo ... –

Respuesta

9

Su instinto de usar variables de entorno es correcto. Sin embargo, existe cierto peligro de ejecutar pruebas unitarias con el DB incorrecto. Además, es posible que no desee connect_db con cada solicitud y en cualquier lugar que desee utilizar db. Puede usar un directorio de configuración y variables de entorno que configure explícitamente. Esto es lo mejor que he encontrado hasta ahora.

run.py 
shell.py 

config/__init__.py 
config/test.py 
config/postgres.py 
... 

main/__init__.py 
main/someapp/__init__.py 
main/someapp/models.py 

... 
main/tests/__init__.py 
main/tests/testutils.py 

así, los archivos de configuración pueden ser:

# config/test.py 
SQLALCHEMY_DATABASE_URI = "sqlite://" 

y

# config/postgres.py 
SQLALCHEMY_DATABASE_URI = 'postgresql://user:[email protected]/somedb' 

lo tanto, puedo establecer explícitamente la db en mi TestCase de base:

import os 
from flask.ext.testing import TestCase 

os.environ["DIAG_CONFIG_MODULE"] = "config.test" 
from main import app, db 


class SQLAlchemyTest(TestCase): 

    def create_app(self): 
     return app 

    def setUp(self): 
     db.create_all() 

    def tearDown(self): 
     db.session.remove() 
     db.drop_all() 

Entonces, el main/__init__.py, para mí:

import os 

from flask import Flask, render_template, g 
from flask.ext.sqlalchemy import SQLAlchemy 

# by default, let's use a DB we don't care about 
# but, we can override if we want 
config_obj = os.environ.get("DIAG_CONFIG_MODULE", "config.test") 
app = Flask(__name__) 
app.config.from_object(config_obj) 
db = SQLAlchemy(app) 

@app.before_request 
def before_request(): 
    g.db = db 
    g.app = app 

# ... 
@app.route('/', methods=['GET']) 
def get(): 
    return render_template('home.html') 
# ...  
from main.someapp.api import mod as someappmod 
app.register_blueprint(someappmod) 

Luego, en los otros archivos, donde sé que lo config Quiero correr, potencialmente:

# run.py 
import os 
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres" 
from main import app 
app.run(debug=True) 

y

# shell.py 
import os 
os.environ["DIAG_CONFIG_MODULE"] = "config.postgres" 

from main import app, db 
from main.symdiag.models import * 
from main.auth.models import * 
print sorted(k for k in locals().keys() if not k.startswith("_")) 
import IPython 
IPython.embed() 

Tal vez .. mejor hasta ahora: P.

6

No querrá hacer que la conexión a la base de datos se realice en el momento de la importación. Continúa y configura tu aplicación en el momento de la importación porque siempre puedes ajustar la configuración en tus pruebas antes de intentar probar o ejecutar tu aplicación. En el siguiente ejemplo, tendrá su conexión db detrás de algunas funciones que usan la configuración de la aplicación para que en una prueba unitaria realmente pueda cambiar la conexión db para apuntar a un archivo diferente y luego continuar y conectarse explícitamente en su configuración.

Digamos que tienes un paquete que contiene miaplicacion myapp.py que se parece a:

# myapp/myapp.py 
from __future__ import with_statement 
from sqlite3 import dbapi2 as sqlite3 
from contextlib import closing 
from flask import Flask, request, session, g, redirect, url_for, abort, \ 
    render_template, flash 

# configuration 
DATABASE = '/tmp/flaskr.db' 
DEBUG = True 
SECRET_KEY = 'development key' 
USERNAME = 'admin' 
PASSWORD = 'default' 

# create our little application :) 
app = Flask(__name__) 
app.config.from_object(__name__) 
app.config.from_envvar('MYAPP_SETTINGS', silent=True) 

def connect_db(): 
    """Returns a new connection to the database.""" 
    return sqlite3.connect(app.config['DATABASE']) 


def init_db(): 
    """Creates the database tables.""" 
    with closing(connect_db()) as db: 
     with app.open_resource('schema.sql') as f: 
      db.cursor().executescript(f.read()) 
     db.commit() 


@app.before_request 
def before_request(): 
    """Make sure we are connected to the database each request.""" 
    g.db = connect_db() 


@app.after_request 
def after_request(response): 
    """Closes the database again at the end of the request.""" 
    g.db.close() 
    return response 

@app.route('/') 
def show_entries(): 
    cur = g.db.execute('select title, text from entries order by id desc') 
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] 
    return render_template('show_entries.html', entries=entries) 

if __name__=="__main__": 
    app.run() 

Su miaplicacion archivo de prueba/test_myapp.py se verá así:

import os 
import myapp 
import unittest 
import tempfile 

class MyappTestCase(unittest.TestCase): 

    def setUp(self): 
     self.db_fd, myapp.app.config['DATABASE'] = tempfile.mkstemp() 
     self.app = myapp.app.test_client() 
     myapp.init_db() 

    def tearDown(self): 
     os.close(self.db_fd) 
     os.unlink(myapp.app.config['DATABASE']) 

    def test_empty_db(self): 
     rv = self.app.get('/') 
     assert 'No entries here so far' in rv.data 

Por supuesto, si usted' Me gustaría usar SQLAlchemy, tendrá que actualizar las funciones connect_db e init_db de forma adecuada, pero espero que tenga la idea.

+0

Mike: En ese fragmento de código, hará la conexión cada vez que llegue una solicitud. ¿No crees que se ralentizaría tu aplicación por una sobrecarga general de la llamada de conexión? –

+2

Sí, no es ideal. Para sqlite3, dudo que lo note, pero para una conexión a una base de datos en red (mysql, postgresql, mssql, mongo, cassandra, etc.) querría un grupo de conexiones para reducir la sobrecarga. – stderr

+0

:) porque encontré ese problema cuando uso mysql –

2

En primer lugar, en lugar de instanciar la aplicación Flask directamente en su secuencia de comandos, utiliza un application factory. Significa que crea una función que toma su archivo de configuración como parámetro y devuelve el objeto de la aplicación instanciada. A continuación, crea el objeto global SQLAlchemy sin parámetro y lo configura al crear la aplicación, as explained here.

db = SQLAlchemy() 

def create_app(configfile): 
    app = Flask(__name__) 

    app.config.from_pyfile(config, silent=True) 
    db.init_app(app) 

    # create routes, etc. 

    return app 

Para ejecutar la aplicación, sólo tiene que hacer algo como:

app = create_app('config.py') 
app.run() 

Para ejecutar unittests, se puede hacer algo como:

class Test(TestCase): 
    def setUp(self): 
     # init test database, etc. 
     app = create_app('test_config.py') 
     self.app = app.test_client() 
    def tearDown(self): 
     # delete test database, etc. 

En mi caso, estoy usando SQLAlchemy directamente con scoped_session en lugar de Flask-SQLAlchemy. Hice lo mismo, pero con Lazy SQLAlchemy setup.

Cuestiones relacionadas