如何在Pyramid应用程序启动期间获取Registry().settings



我习惯于在Django和gunicorn上开发web应用程序。

在Django的情况下,Django应用程序中的任何应用程序模块都可以通过Django.conf.settings获得部署设置。"settings.py"是用Python编写的,因此任何任意设置和预处理都可以动态地定义

在gunicorn的情况下,它按优先级有三个配置位置,一个设置注册表类实例将这些位置组合在一起。(但通常情况下,这些设置仅用于枪支而非应用。)

  1. 命令行参数
  2. 配置文件。(像Django,用Python可以具有任意动态设置。)
  3. 粘贴应用程序设置

在Pyramid的情况下,根据Pyramid文档,部署设置通常可以放入Pyramid.registry().settings中。但似乎只有当存在Pyramid.router.router()实例时才能访问它。这就是pyramid.threadlocal.get_current_registry()。在应用程序"main.py"的启动过程中,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']}))
    

然后,其他模块可以通过"导入myapp.settings"获得任意的部署设置。或者,如果Registry().settings比"settings.py"更可取,则在"main.py"启动过程中,**settings-kargs和"settings-py"可以合并并注册到Registry(,).settions中。

无论如何,如何在启动时获得设置dictionay?或者,Pyramid温和地迫使我们将需要部署设置的每一个代码都放在"视图"可调用程序中,这些可调用程序可以通过request.register.settings随时获取设置字典?


编辑

谢谢,迈克尔和克里斯。

我终于明白为什么Pyramid使用线程本地变量(注册表和请求),尤其是多个Pyramid应用程序的注册表对象。

然而,在我看来,部署设置通常会影响业务逻辑,这些逻辑可能会定义特定于应用程序的东西。这些逻辑通常放在一个或多个Python模块中,这些模块可能不是"app/init.py"或"app/views.py",可以轻松访问Config()或Registry()。这些Python模块通常是Python进程级别的"全局"模块。

也就是说,即使多个Pyramid应用程序共存,尽管它们有自己的线程局部变量,但它们也必须共享那些"全局"Python模块,这些模块可能包含Python进程级别的特定应用程序。

当然,每个模块都可以有"initialize()"调用,它是由应用程序"main"可调用的Configurator()调用的,或者通过一系列函数调用传递Registry()或Request()对象可以满足通常的要求。但是,我想Pyramid的创建者(像我一样)或拥有"大型应用程序或如此多设置"的开发人员可能会感到麻烦,尽管这就是Pyramid设计。

所以,我认为Registry().settings应该只有真正的"线程本地"变量,而不应该有正常的业务逻辑设置。开发人员应负责分离多个特定于应用程序的模块、类、可调用变量等。到目前为止,从我的角度来看,我会接受克里斯的回答。或者在"main"可调用中,执行"execfile('settings.py',settings,settings)"并将其放在某个"全局"空间中。

另一个选项,如果您喜欢通过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"是可以在其中找到配置文件的目录名。

我使用的模式是将Configurator传递给需要初始化的模块。Pyramid不使用任何全局变量,因为设计目标是能够在同一过程中运行Pyramid的多个实例。threadlocals是全局的,但它们对当前请求是本地的,因此不同的Pyramid应用程序可以同时从不同的线程推送到它们。

考虑到这一点,如果你确实想要一个全局设置词典,你必须自己处理。您甚至可以通过调用config.begin()将注册表推送到线程本地管理器上。

我认为这里需要注意的主要一点是,您不应该在模块级别调用get_current_registry(),因为在导入时,您并不能真正保证线程局部变量已经初始化,但是在init_model()函数中,如果您调用get_current_registry(),那么如果您以前调用过config.begin(),那就没问题了。

很抱歉,这有点复杂,但这是一个常见的问题,最好的答案是:将配置程序传递给需要它的子模块,并允许它们向注册表/设置对象添加内容,以便稍后使用

Pyramid使用PasteDeploy的静态配置,这与Django不同。您的[EDIT]部分是一个不错的解决方案,我认为Pyramid社区应该考虑这样的用法。

相关内容

最新更新