如何在Pyramid应用启动时获取Registry().settings?

21 投票
3 回答
8909 浏览
提问于 2025-04-16 20:27

我习惯在Django和gunicorn上开发网页应用。

在Django中,任何应用模块都可以通过django.conf.settings获取部署设置。这个“settings.py”是用Python写的,所以可以动态定义任何设置和预处理。

对于gunicorn来说,它有三个配置位置,优先级依次为:一个设置注册类实例将这些配置结合在一起。(不过通常这些设置只用于gunicorn,而不是应用本身。)

  1. 命令行参数。
  2. 配置文件。(像Django一样,用Python写,可以动态设置任何内容。)
  3. 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 个回答

1

Pyramid使用的是静态配置,通过PasteDeploy来实现,这和Django不一样。你提到的[EDIT]部分是个不错的解决方案,我觉得Pyramid社区应该考虑这种用法。

13

我使用的模式是把Configurator传递给那些需要初始化的模块。Pyramid这个框架不使用全局变量,因为它的设计目标是能够在同一个进程中运行多个Pyramid实例。虽然线程局部存储是全局的,但它们只对当前请求有效,所以不同的Pyramid应用可以在不同的线程中同时使用这些存储。

考虑到这一点,如果你确实需要一个全局的设置字典,你就得自己处理这个问题。你甚至可以通过调用config.begin(),把注册表推送到线程局部管理器中。

我认为这里最重要的一点是,你不应该在模块级别调用get_current_registry(),因为在导入时并不能保证线程局部存储已经初始化。然而,如果在你的init_model()函数中调用get_current_registry(),只要你之前调用过config.begin(),那就没问题。

抱歉这有点复杂,但这是一个常见的问题,最好的答案是:把配置器传递给需要它的子模块,让它们可以向注册表/设置对象添加内容,以便后续使用

17

还有一个选择,如果你喜欢通过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"是可以找到配置文件的目录名称。

撰写回答