配置
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