Skip to content

配置

Starlette 鼓励按照十二要素模式将配置与代码严格分离。

配置应存储在环境变量中,或存储在未提交到源代码控制的 .env 文件中。

from sqlalchemy import create_engine
from starlette.applications import Starlette
from starlette.config import Config
from starlette.datastructures import CommaSeparatedStrings, Secret

# Config will be read from environment variables and/or ".env" files.
config = Config(".env")

DEBUG = config('DEBUG', cast=bool, default=False)
DATABASE_URL = config('DATABASE_URL')
SECRET_KEY = config('SECRET_KEY', cast=Secret)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)

app = Starlette(debug=DEBUG)
engine = create_engine(DATABASE_URL)
...


# Don't commit this to source control.
# Eg. Include ".env" in your `.gitignore` file.
DEBUG=True
DATABASE_URL=postgresql://user:password@localhost:5432/database
SECRET_KEY=43n080musdfjt54t-09sdgr
ALLOWED_HOSTS=127.0.0.1, localhost

配置优先级

配置值的读取顺序为:

  • 从环境变量中。
  • .env 文件。
  • config 中给出的默认值。

如果这些都不匹配,那么 config(...) 将引发一个错误。

秘密

对于敏感密钥, Secret 类是有用的,因为它有助于最大程度地减少其所持值可能泄漏到回溯或其他代码内省中的情况。

要获取 Secret 实例的值,必须将其显式地转换为字符串。您应该仅在使用该值的位置进行此操作。

>>> from myproject import settings
>>> settings.SECRET_KEY
Secret('**********')
>>> str(settings.SECRET_KEY)
'98n349$%8b8-7yjn0n8y93T$23r'

提示

You can use DatabaseURL from databases package here to store database URLs and avoid leaking them in the logs.

逗号分隔字符串

对于在单个配置键中容纳多个内容, CommaSeparatedStrings 类型是有用的。

>>> from myproject import settings
>>> print(settings.ALLOWED_HOSTS)
CommaSeparatedStrings(['127.0.0.1', 'localhost'])
>>> print(list(settings.ALLOWED_HOSTS))
['127.0.0.1', 'localhost']
>>> print(len(settings.ALLOWED_HOSTS))
2
>>> print(settings.ALLOWED_HOSTS[0])
'127.0.0.1'

读取或修改环境

在某些情况下,您可能想要以编程方式读取或修改环境变量。这在测试中特别有用,在测试中您可能想要覆盖环境中的特定键。

您应该使用 Starlette 的 environ 实例,而不是从 os.environ 进行读取或写入。此实例映射到标准的 os.environ 上,如果在配置已经读取了任何环境变量之后又对其进行设置,该实例会通过引发错误来为您提供额外的保护。

如果您正在使用 pytest ,那么您可以在 tests/conftest.py 中设置任何初始环境。

from starlette.config import environ

environ['DEBUG'] = 'TRUE'

读取带前缀的环境变量

您可以通过设置 env_prefix 参数来对环境变量进行命名空间划分。

import os

from starlette.config import Config

os.environ['APP_DEBUG'] = 'yes'
os.environ['ENVIRONMENT'] = 'dev'

config = Config(env_prefix='APP_')

DEBUG = config('DEBUG') # lookups APP_DEBUG, returns "yes"
ENVIRONMENT = config('ENVIRONMENT') # lookups APP_ENVIRONMENT, raises KeyError as variable is not defined

一个完整的示例

构建大型应用程序可能很复杂。您需要适当地分离配置和代码,在测试期间进行数据库隔离,使用单独的测试和生产数据库等等。

在这里,我们将看一个完整的示例,该示例展示了我们如何开始构建一个应用程序。

首先,让我们将我们的设置、我们的数据库表定义以及我们的应用逻辑分开:

from starlette.config import Config
from starlette.datastructures import Secret

config = Config(".env")

DEBUG = config('DEBUG', cast=bool, default=False)
SECRET_KEY = config('SECRET_KEY', cast=Secret)

DATABASE_URL = config('DATABASE_URL')


import sqlalchemy

# Database table definitions.
metadata = sqlalchemy.MetaData()

organisations = sqlalchemy.Table(
    ...
)


from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.routing import Route

from myproject import settings


async def homepage(request):
    ...

routes = [
    Route("/", endpoint=homepage)
]

middleware = [
    Middleware(
        SessionMiddleware,
        secret_key=settings.SECRET_KEY,
    )
]

app = Starlette(debug=settings.DEBUG, routes=routes, middleware=middleware)

现在让我们处理我们的测试配置。我们想要在每次测试套件运行时创建一个新的测试数据库,并在测试完成后将其删除。我们还想要确保

from starlette.config import environ
from starlette.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy_utils import create_database, database_exists, drop_database

# This line would raise an error if we use it after 'settings' has been imported.
environ['DEBUG'] = 'TRUE'

from myproject import settings
from myproject.app import app
from myproject.tables import metadata


@pytest.fixture(autouse=True, scope="session")
def setup_test_database():
    """
    Create a clean test database every time the tests are run.
    """
    url = settings.DATABASE_URL
    engine = create_engine(url)
    assert not database_exists(url), 'Test database already exists. Aborting tests.'
    create_database(url)             # Create the test database.
    metadata.create_all(engine)      # Create the tables.
    yield                            # Run the tests.
    drop_database(url)               # Drop the test database.


@pytest.fixture()
def client():
    """
    Make a 'client' fixture available to test cases.
    """
    # Our fixture is created within a context manager. This ensures that
    # application lifespan runs for every test case.
    with TestClient(app) as test_client:
        yield test_client