如何在Pyramid应用启动时获取Registry().settings?
我习惯在Django和gunicorn上开发网页应用。
在Django中,任何应用模块都可以通过django.conf.settings获取部署设置。这个“settings.py”是用Python写的,所以可以动态定义任何设置和预处理。
对于gunicorn来说,它有三个配置位置,优先级依次为:一个设置注册类实例将这些配置结合在一起。(不过通常这些设置只用于gunicorn,而不是应用本身。)
- 命令行参数。
- 配置文件。(像Django一样,用Python写,可以动态设置任何内容。)
- Paster应用设置。
在Pyramid中,根据Pyramid的文档,部署设置通常放在pyramid.registry.Registry().settings中。但似乎只有在存在pyramid.router.Router()实例时才能访问。也就是说,在应用的“main.py”启动过程中,pyramid.threadlocal.get_current_registry().settings返回的是None。
例如,我通常在SQLAlchemy模型模块中定义一些业务逻辑,这需要部署设置,如下所示。
myapp/models.py
from sqlalchemy import Table, Column, Types
from sqlalchemy.orm import mapper
from pyramid.threadlocal import get_current_registry
from myapp.db import session, metadata
settings = get_current_registry().settings
mytable = Table('mytable', metadata,
Column('id', Types.INTEGER, primary_key=True,)
(other columns)...
)
class MyModel(object):
query = session.query_property()
external_api_endpoint = settings['external_api_uri']
timezone = settings['timezone']
def get_api_result(self):
(interact with external api ...)
mapper(MyModel, mytable)
但是,“settings['external_api_endpoint']”会引发TypeError异常,因为“settings”是None。
我想到了两个解决方案。
在“models.py”中定义一个可调用的函数,接受“config”参数,然后在“main.py”中用Configurator()实例调用它。
myapp/models.py
from sqlalchemy import Table, Column, Types from sqlalchemy.orm import mapper from myapp.db import session, metadata _g = globals() def initialize(config): settings = config.get_settings() mytable = Table('mytable', metadata, Column('id', Types.INTEGER, rimary_key = True,) (other columns ...) ) class MyModel(object): query = session.query_property() external_api_endpoint = settings['external_api_endpoint'] def get_api_result(self): (interact with external api)... mapper(MyModel, mytable) _g['MyModel'] = MyModel _g['mytable'] = mytable
或者,创建一个空模块“app/settings.py”,稍后再把设置放进去。
myapp/__init__.py
from pyramid.config import Configurator from .resources import RootResource def main(global_config, **settings): config = Configurator( settings = settings, root_factory = RootResource, ) import myapp.settings myapp.setting.settings = config.get_settings() (other configurations ...) return config.make_wsgi_app()
这两种方案和其他方案都能满足需求,但我觉得有点麻烦。我想要的是以下内容。
development.ini
定义大致的设置,因为development.ini只能有字符串类型的常量。
[app:myapp] use = egg:myapp env = dev0 api_signature = xxxxxx
myapp/settings.py
根据development.ini定义详细设置,因为可以设置任何类型的变量。
import datetime, urllib from pytz import timezone from pyramid.threadlocal import get_current_registry pyramid_settings = get_current_registry().settings if pyramid_settings['env'] == 'production': api_endpoint_uri = 'http://api.external.com/?{0}' timezone = timezone('US/Eastern') elif pyramid_settings['env'] == 'dev0': api_endpoint_uri = 'http://sandbox0.external.com/?{0}' timezone = timezone('Australia/Sydney') elif pyramid_settings['env'] == 'dev1': api_endpoint_uri = 'http://sandbox1.external.com/?{0}' timezone = timezone('JP/Tokyo') api_endpoint_uri = api_endpoint_uri.format(urllib.urlencode({'signature':pyramid_settings['api_signature']}))
然后,其他模块可以通过“import myapp.settings”获取任意的部署设置。或者,如果Registry().settings比“settings.py”更合适,**settings关键字参数和“settings.py”可以在“main.py”启动过程中结合并注册到Registry().settings中。
总之,如何在启动时获取设置字典?或者,Pyramid是否强制我们把所有需要部署设置的代码放在“views”可调用中,这样可以随时通过request.registry.settings获取设置字典?
编辑
谢谢,Michael和Chris。
我终于明白为什么Pyramid使用线程局部变量(注册和请求),特别是注册对象用于多个Pyramid应用。
不过在我看来,部署设置通常会影响业务逻辑,这些逻辑可能会定义特定于应用的内容。这些逻辑通常放在一个或多个Python模块中,可能不在“app/init.py”或“app/views.py”中,这些模块可以轻松访问Config()或Registry()。这些Python模块通常在Python进程级别是“全局”的。
也就是说,即使有多个Pyramid应用共存,尽管它们有自己的线程局部变量,但它们仍然需要共享那些可能包含特定于应用内容的“全局”Python模块。
当然,所有这些模块都可以有一个“initialize()”可调用函数,这个函数可以通过应用的“main”可调用函数与Configurator()一起调用,或者通过一系列函数调用传递Registry()或Request()对象来满足常规需求。但是,我想Pyramid的初学者(像我一样)或有“大型应用或许多设置”的开发者可能会觉得麻烦,尽管这是Pyramid的设计。
所以,我认为Registry().settings应该只包含真正的“线程局部”变量,而不应该有普通的业务逻辑设置。多个特定于应用的模块、类、可调用变量等的分离责任应该由开发者来承担。就我目前的观点,我会采纳Chris的回答。或者在“main”可调用函数中,执行“execfile('settings.py', settings, settings)”并将其放入某个“全局”空间。
3 个回答
Pyramid使用的是静态配置,通过PasteDeploy来实现,这和Django不一样。你提到的[EDIT]部分是个不错的解决方案,我觉得Pyramid社区应该考虑这种用法。
我使用的模式是把Configurator
传递给那些需要初始化的模块。Pyramid这个框架不使用全局变量,因为它的设计目标是能够在同一个进程中运行多个Pyramid实例。虽然线程局部存储是全局的,但它们只对当前请求有效,所以不同的Pyramid应用可以在不同的线程中同时使用这些存储。
考虑到这一点,如果你确实需要一个全局的设置字典,你就得自己处理这个问题。你甚至可以通过调用config.begin()
,把注册表推送到线程局部管理器中。
我认为这里最重要的一点是,你不应该在模块级别调用get_current_registry()
,因为在导入时并不能保证线程局部存储已经初始化。然而,如果在你的init_model()
函数中调用get_current_registry()
,只要你之前调用过config.begin()
,那就没问题。
抱歉这有点复杂,但这是一个常见的问题,最好的答案是:把配置器传递给需要它的子模块,让它们可以向注册表/设置对象添加内容,以便后续使用。
还有一个选择,如果你喜欢通过Python进行全局配置,可以创建一个settings.py文件。如果这个文件需要从ini文件中获取值,就解析ini文件并提取这些值(在模块范围内,这样在导入时就会运行):
from paste.deploy.loadwsgi import appconfig
config = appconfig('config:development.ini', 'myapp', relative_to='.')
if config['env'] == 'production':
api_endpoint_uri = 'http://api.external.com/?{0}'
timezone = timezone('US/Eastern')
# .. and so on ...
'config:development.ini'是ini文件的名称(前面加上'config:')。'myapp'是配置文件中代表你应用的部分名称(例如:[app:myapp])。"relative_to"是可以找到配置文件的目录名称。