如何在Pyramid app启动时获取Registry().设置?

tak*_*mag 21 python paste pyramid

我习惯在Django和gunicorn上开发Web应用程序.

对于Django,Django应用程序中的任何应用程序模块都可以通过django.conf.settings获取部署设置."settings.py"是用Python编写的,因此可以动态定义任意设置和预处理.

在gunicorn的情况下,它按优先顺序有三个配置位置,并且一个设置注册表类实例组合这些.(但通常这些设置仅用于gunicorn而不是应用程序.)

  1. 命令行参数.
  2. 配置文件.(像Django一样,用Python编写,可以动态地设置任意设置.)
  3. 贴纸应用程序设置.

对于Pyramid,根据Pyramid文档,部署设置通常可以放入pyramid.registry.Registry().settings中.但它似乎仅在存在pyramid.router.Router()实例时才被访问.那就是pyramid.threadlocal.get_current_registry().settings在应用程序"main.py"的启动过程中返回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)
Run Code Online (Sandbox Code Playgroud)

但是,"settings ['external_api_endpoint']"会引发TypeError异常,因为"settings"为None.

我想了两个解决方案.

  • 定义一个callable,它接受"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
    
    Run Code Online (Sandbox Code Playgroud)
  • 或者,将一个空模块"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()
    
    Run Code Online (Sandbox Code Playgroud)

两种解决方案都符合要求,但我觉得很麻烦.我想要的是以下内容.

  • development.ini

    定义粗略设置,因为development.ini只能有字符串类型常量.

    [app:myapp]
    use = egg:myapp
    env = dev0
    api_signature = xxxxxx
    
    Run Code Online (Sandbox Code Playgroud)
  • 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']}))
    
    Run Code Online (Sandbox Code Playgroud)

然后,其他模块可以通过"import myapp.settings"获得任意部署设置.或者,如果Registry().设置优于"settings.py",**设置kwargs和"settings.py"可以组合并在"main.py"启动过程中注册到Registry().settings中.

无论如何,如何在启动时获取设置词典?或者,Pyramid会轻轻地强制我们将需要部署设置的每个代码放在"views"callables中,这些代码可以通过request.registry.settings随时获取设置字典?


编辑

谢谢,迈克尔和克里斯.

我终于理解为什么Pyramid使用threadlocal变量(注册表和请求),特别是多个Pyramid应用程序的注册表对象.

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

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

当然,每个模块都可以有"initialize()"callalbe,它由应用程序"main"可调用的Configurator()调用,或通过Registory()或Request()对象通过如此长的一系列函数调用可以满足通常要求.但是,我猜金字塔开头(像我一样)或开发人员有"大应用程序或许多设置"可能会觉得麻烦,虽然那是金字塔设计.

因此,我认为,Registry().设置应该只有真正的"线程局部"变量,并且不应该具有正常的业务逻辑设置.开发人员应该对多个特定于应用程序的模块,类,可调用变量等的隔离负责.截至目前,从我的观点来看,我将采取克里斯的答案.或者在"main"可调用中,执行"execfile('settings.py',settings,settings)"并将其放在某个"全局"空间中.

Chr*_*ugh 16

另一个选择,如果您喜欢通过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 ...
Run Code Online (Sandbox Code Playgroud)

'config:development.ini'是ini文件的名称(前缀为'config:').'myapp'是配置文件中代表您的应用的部分名称(例如[app:myapp])."relative_to"是可以在其中找到配置文件的目录名称.


Mic*_*kel 13

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

考虑到这一点,如果你想要一个全局设置词典,你将不得不自己处理.您甚至可以通过调用将注册表推送到threadlocal管理器config.begin().

我认为这里最重要的一点就是你不应该get_current_registry()在模块级别调用,因为在导入时你并不能保证threadlocals被初始化,但是init_model()如果你打电话get_current_registry(),你的函数就是你的'如果你以前打过电话,那就好了config.begin().

对不起,这有点令人费解,但这是一个常见的问题,最好的答案是:将配置程序传递给需要它的子模块,并允许它们将内容添加到注册表/设置对象中以供日后使用.

  • 扩展Pyramid应用程序的推荐方法是通过`config.include`机制,它将调用外部方法并将配置对象传递给它.这是拉入所有模块并允许它们在启动时自行配置的好方法. (3认同)