21

我习惯在 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()实例时才能访问。也就是说,在应用程序“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()
    

和其他方案都满足要求,但我觉得麻烦。我想要的是以下内容。

  • 开发.ini

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

    [app:myapp]
    use = egg:myapp
    env = dev0
    api_signature = xxxxxx
    
  • 我的应用程序/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 kwargs 和 "settings.py" 可以在 "main.py" 启动过程中合并并注册到 Registry().settings 中。

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


编辑

谢谢,迈克尔和克里斯。

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

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

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

当然,每个这些模块都可以有“initialize()”callalbe,它由应用程序“main”可调用的 Configurator() 调用,或者通过这么长的一系列函数调用传递 Registory() 或 Request() 对象可以满足通常的需求要求。但是,我猜 Pyramid 初学者(如我)或拥有“大型应用程序或这么多设置”的开发人员可能会觉得麻烦,尽管这是 Pyramid 设计。

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

4

3 回答 3

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”是可以找到配置文件的目录名。

于 2011-06-29T08:29:33.567 回答
13

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

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

我认为这里要带走的主要事情是你不应该get_current_registry()在模块级别调用,因为在导入时你并不能真正保证 threadlocals 被初始化,但是init_model()如果你调用你的函数get_current_registry(),你'如果你之前打电话给config.begin().

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

于 2011-06-29T08:06:38.290 回答
1

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

于 2011-08-05T12:03:52.110 回答