我一直在思考WSGI应用程序的工厂模式,as recommended by the Flask docs。特别是关于那些通常被显示为使用在模块导入时创建的对象的函数,例如示例中的db
,而不是在工厂函数中创建的。
工厂是否可以理想地创建_everything__new或不会对db
引擎等对象有意义?
(我在这里想到更清晰的分离和更好的可测试性。)
这是一些代码,我试图完成为wsgi应用程序创建所有需要的对象。在其工厂功能。
# factories.py
def create_app(config, engine=None):
"""Create WSGI application to be called by WSGI server. Full factory function
that takes care to deliver entirely new WSGI application instance with all
new member objects like database engine etc.
Args:
config (dict): Dict to update the wsgi app. configuration.
engine (SQLAlchemy engine): Database engine to use.
"""
# flask app
app = Flask(__name__) # should be package name instead of __name__ acc. to docs
app.config.update(config)
# create blueprint
blueprint = ViewRegistrationBlueprint('blueprint', __name__, )
# request teardown behaviour, always called, even on unhandled exceptions
# register views for blueprint
from myapp.views import hello_world
# dynamically scrapes module and registers methods as views
blueprint.register_routes(hello_world)
# create engine and request scoped session for current configuration and store
# on wsgi app
if (engine is not None):
# delivers transactional scope when called
RequestScopedSession = scoped_session(
sessionmaker(bind=engine),
scopefunc=flask_request_scope_func
)
def request_scoped_session_teardown(*args, **kwargs):
"""Function to register and call by the framework when a request is finished
and the session should be removed.
"""
# wrapped in try/finally to make sure no error collapses call stack here
try:
RequestScopedSession.remove() # rollback all pending changes, close and return conn. to pool
except Exception as exception_instance:
msg = "Error removing session in request teardown.\n{}"
msg = msg.format(exception_instance)
logger.error(msg)
finally:
pass
app.config["session"] = RequestScopedSession
blueprint.teardown_request(request_scoped_session_teardown)
# register blueprint
app.register_blueprint(blueprint)
return app
def create_engine(config):
"""Create database engine from configuration
Args:
config (dict): Dict used to assemble the connection string.
"""
# connection_string
connection_string = "{connector}://{user}:{password}@{host}/{schema}"
connection_string = connection_string.format(**config)
# database engine
return sqlalchemy_create_engine(
connection_string,
pool_size=10,
pool_recycle=7200,
max_overflow=0,
echo=True
)
# wsgi.py (served by WSGI server)
from myapp.factories import create_app
from myapp.factories import create_engine
from myapp.configuration.config import Config
config = Config()
engine = create_engine(config.database_config)
app = create_app(config.application_config, engine=engine)
# conftest.py
from myapp.factories import create_app
from myapp.factories import create_engine
from myapp.configuration.config import Config
@pytest.fixture
def app():
config = TestConfig()
engine = create_engine(config.database_config)
app = create_app(config.application_config, engine=engine)
with app.app_context():
yield app
答案 0 :(得分:0)
正如您也使用sanic
标记了这一点,我将以该背景回答。 Sanic是异步的,因此依赖于事件循环。事件循环是一种资源,因此不能在测试之间共享,而是为每个测试重新创建。因此,还需要为每个测试创建数据库连接等,并且不能重新使用它,因为它是异步的并且取决于事件循环。即使没有异步性质,每次测试创建数据库连接也是最干净的,因为它们具有状态(如临时表)。
所以我最终得到了一个create_app()
,它创建了所有允许我在测试运行中创建任意数量的独立应用程序的东西。 (说实话,有一些全局资源,比如已注册的事件监听器,但是使用py.test工厂可以很容易地将它们拆除。)为了测试性,我会尝试避免在模块导入时创建的全局资源。虽然我在大型和成功的项目中看到的不同。
这不是一个明确的答案,我知道......