当前位置:首页>python>本地、测试、生产环境配置分离:Python 多环境配置管理实践方案

本地、测试、生产环境配置分离:Python 多环境配置管理实践方案

  • 2026-09-09 12:21:05
本地、测试、生产环境配置分离:Python 多环境配置管理实践方案

在一个完整的 Python 项目生命周期中,通常涉及本地开发环境、测试环境以及生产环境。其中,本地开发环境往往因开发者个人习惯或系统差异而存在不同配置;而测试与生产环境则多为团队共享,配置要求相对统一且稳定。在这种情况下,如何实现项目在不同环境(服务器)之间部署时,无需手动修改配置文件呢?

方式一:dynaconf

dynaconf(https://www.dynaconf.com/) 支持多种配置文件格式,如 TOML、YAML、JSON、INI、Python、Dotenv 等,并且支持动态加载配置。

目录结构如下:

|——config
|       .secrets.toml
|       dev.toml
|       dev.local.toml
|       test.toml
|       settings.toml
|       online.toml
|       __init__.py
|  .gitignore

settings.toml

[global]  # 全局配置,优先级高于默认配置
TABLE_PREFIX
 = "wd_"

[default]
 # 默认配置,优先级最低
TABLE_PREFIX
 = "df_"
LOG_LEVEL
 = "DEBUG"
db_url
 = "@format {this.db_config.host}:{this.db_config.port}"

dev.toml

[dev]
TABLE_PREFIX
 = "dev_"  # 环境配置,优先级高于全局配置
LOG_LEVEL
 = "DEBUG"
DEBUG
 = true

# 第一种方式配置嵌套对象

[dev.db_config]

host
 = "127.0.0.1"
port
 = 3306
name
 = "toml"
user
 = "dev"
pwd
 = "123456"

test.toml

[test]
TABLE_PREFIX
="test_"
LOG_LEVEL
 = "INFO"
DEBUG
 = false

# 第二种方式配置嵌套对象

db_config.host
 = "172.168.1.105"
db_config.port
 = 3306
db_config.name
 = "toml"
db_config.user
 = "test"
db_config.pwd
 = "xxxxx"

__init__.py

from dynaconf import Dynaconf

Settings = Dynaconf(
    # 环境变量的前缀,比如你有一个配置项为 DEBUG,那么可以通过设置环境变量 WDADMIN_DEBUG = True 来设置 DEBUG 的值

    envvar_prefix="WDADMIN",
    # 配置文件的根路径

    root_path="",
    settings_files=['settings.toml', 'dev.toml', 'test.toml', '.secrets.toml'],
    # 使用分层的环境配置,为True时 default、global等分层的配置才生效

    environments=True,
    # 分层环境默认使用的配置,默认为 development

    env="dev",  # env 对应的环境变量 ENV_FOR_DYNACONF,如设置环境变量 ENV_FOR_DYNACONF=dev
    # 修改切换env的环境变量名,默认是 ENV_FOR_DYNACONF,现在改为 ENV_WDADMIN

    env_switcher="ENV_WDADMIN",  # env_switcher 对应的环境变量 ENVVAR_SWITCHER_FOR_DYNACONF

    # 是否加载.env文件

    load_dotenv=False,
)

使用方式如下:

from config import settings
import
 os

# 通过环境变量设置某个配置项

# os.environ["WDADMIN_DEBUG"] = True


# 使用不同的环境配置:开发、测试、生产环境

os.environ["ENV_WDADMIN"] = "test"

print
(f"{settings.TABLE_PREFIX=}")
print
(f"{settings.LOG_LEVEL=}")
print
(f"{settings.db_config=}")
print
(f"{settings.db_url=}")

## 输出结果:

## settings.TABLE_PREFIX='test_'

## settings.LOG_LEVEL='INFO'

## settings.db_config=<Box: {'host': '172.168.1.105', 'port': 3306, 'name': 'toml', 'user': 'test', 'pwd': 'xxxxx'}>

## settings.db_url='172.168.1.105:3306'
  1. 1. 多种配置方式,dynaconf后加载的配置文件会覆盖前面的配置,所以文件的加载顺序和优先级顺序是相反的。
  2. 2. 如果存在对应的*.local.toml文件,dynaconf会自动加载,不需要写额外的代码。
  3. 3. 优先级: load_dotenv【.env文件】 > environments【环境变量】 > settings_files(local)【.local配置文件】 > settings_files【配置文件】
  4. 4. 事实上,不同环境的配置可以全部写在同一个配置文件中。

例如:settings.toml

[global] # 全局配置,优先级低于当前环境配置
TABLE_PREFIX
 = "wd_"

[default]
  # 默认配置,优先级低于全局配置
TABLE_PREFIX
 = "df_"
LOG_LEVEL
 = "DEBUG"

db_url
 = "@format {this.db_config.host}:{this.db_config.port}"

[dev]

TABLE_PREFIX
 = "dev_"
LOG_LEVEL
 = "DEBUG"
DEBUG
 = true

[dev.db_config]
 # 第一种方式配置嵌套对象
host
 = "127.0.0.1"
port
 = 3306
name
 = "toml"
user
 = "dev"
pwd
 = "123456"

[test]

TABLE_PREFIX
="test_"
LOG_LEVEL
 = "DEBUG"
DEBUG
 = false

# 第二种方式配置嵌套对象

db_config.host
 = "172.168.1.105"
db_config.port
 = 3306
db_config.name
 = "toml"
db_config.user
 = "test"
db_config.pwd
 = "xxxxx"

方式二:python文件

目录结构如下:

|——config
│      dev.py
|      dev_local.py
│      online.py
│      test.py
│      __init__.py
|  .gitignore

dev.py

DEBUG = True

DB_CONF = {
    'ENGINE'
: 'django.db.backends.sqlite3',
    'NAME'
: 'db.sqlite3',
}

test.py

DEBUG = False

DB_CONF = {
    "ENGINE"
: 'django.db.backends.mysql',
    'NAME'
: 'server',
    'USER'
: 'root',
    'PASSWORD'
: 'root123456',
    'HOST'
: '127.0.0.1',
    'PORT'
: '3306'
}

__init__.py

import os

ENV_NAME = "WDADMIN_ENV"
env = os.getenv(ENV_NAME)
if
 not env:
    raise
 Exception(f"get {ENV_NAME} failed, set it IN CAUTION!!!")
elif
 env == "online":
    from
 .online import *
elif
 env == "test":
    from
 .test import *
elif
 env == "dev":
    try
:
        from
 .dev_local import *
    except
 ImportError:
        from
 .dev import *
else
:
    raise
 Exception(f"{ENV_NAME} not in (online, test, dev)")


MEDIA_URL = "media/"

在需要配置项的地方直接导入即可,简单粗暴。这也是在这些配置库没出来前,我们团队一直使用的方式。

方式三:pydantic_settings

pydantic_settings(https://docs.pydantic.org.cn/latest/api/pydantic_settings/) 不仅可以对配置字段进行检查,并且可以使用.来提示访问属性,使用上更方便。其支持 .env、toml、yaml、json 等文件,并支持自定义扩展,比如从配置中心获取配置。

使用.env文件

目录结构大差不差:

|——settings
│      .env.dev
|      .env.dev.local
│      .env.test
│      .env.online
│      __init__.py
|  .gitignore

.env.dev

DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=env
DB_USER=dev
DB_PWD=123456

APP_LOG_LEVEL=DEBUG

.env.test

DB_HOST=172.168.1.105
DB_PORT=3306
DB_NAME=env
DB_USER=test
DB_PWD=xxxxx

APP_LOG_LEVEL=INFO

__init__.py

import os
from
 pathlib import Path
from
 functools import partial
from
 pydantic_settings import BaseSettings, SettingsConfigDict
from
 pydantic import Field, IPvAnyAddress, computed_field


def
 get_env_file():
    env = os.getenv("MY_ENV", "dev")
    file_map = {
        "dev"
: ".env.dev",
        "test"
: ".env.test",
        "online"
: ".env.online",
    }
    file = file_map.get(env)
    if
 file is None:
        raise
 Exception(f"Unknow {env}")
    cur_dir = Path(__file__).resolve().parent
    if
 env == "dev" and (cur_dir / ".env.dev.local").exists():
        return
 cur_dir / ".env.dev.local"
    return
 cur_dir / file


# .env文件中配置项名称规范是全部大写

PSettingsConfigDict = partial(
    SettingsConfigDict, 
    env_file=get_env_file(), 
    case_sensitive=False, 
    extra="ignore")


class
 DBConfig(BaseSettings):
    host: IPvAnyAddress = "127.0.0.1"
    port: int = Field(default=3306, gt=1000, lt=65535)
    name: str = None
    user: str = None
    pwd: str = None

    @computed_field

    @property

    def
 url(self) -> str:
        return
 f"{self.host}:{self.port}@{self.user}:{self.pwd}"

    model_config = PSettingsConfigDict(env_prefix="DB_")


class
 MySettings(BaseSettings):
    APP_NAME: str = "MyAPP"
    LOG_LEVEL: str = "INFO"
    DB_CONFIG: DBConfig = DBConfig()

    model_config = PSettingsConfigDict(env_prefix="APP_", case_sensitive=True)


Settings = MySettings()

用起来爽,就是代码量有点多。为防止变量名冲突,需要使用前缀来定义不同的配置项。

使用方式如下:

from settings import Settings


print
(Settings.LOG_LEVEL)
print
(Settings.DB_CONFIG.url)
print
(Settings.DB_CONFIG.model_dump())
## 输出:

## DEBUG

## 127.0.0.1:3307@devlocal:123456

## {'host': IPv4Address('127.0.0.1'), 'port': 3307, 'name': 'env', 'user': 'devlocal', 'pwd': '123456', 'url': '127.0.0.1:3307@devlocal:123456'}

使用toml文件

目录结构如下:

|——settings
│      dev.toml
|      dev.local.toml
│      test.toml
│      online.toml
│      __init__.py
|——.gitignore

dev.toml

LOG_LEVEL = "DEBUG"

DEBUG
 = true

[DB_CONFIG]

host
 = "127.0.0.1"
port
 = 3306
name
 = "toml"
user
 = "dev"
pwd
 = "123456"

test.toml

LOG_LEVEL = "DEBUG"

DEBUG
 = false

[DB_CONFIG]

host
 = "172.168.1.105"
port
 = 3306
name
 = "toml"
user
 = "test"
pwd
 = "xxxxx"

__init__.py

import os
from
 pathlib import Path
from
 pydantic_settings import (
    BaseSettings, SettingsConfigDict, PydanticBaseSettingsSource,
    TomlConfigSettingsSource,
)
from
 pydantic import Field, IPvAnyAddress, computed_field, BaseModel


def
 get_toml_file():
    env = os.getenv("MY_ENV", "dev")
    file_map = {
        "dev"
: "dev.toml",
        "test"
: "test.toml",
        "online"
: "online.toml",
    }
    file = file_map.get(env)
    if
 file is None:
        raise
 Exception(f"Unknow {env}")
    cur_dir = Path(__file__).resolve().parent
    if
 env == "dev" and (cur_dir / "dev.local.toml").exists():
        return
 cur_dir / "dev.local.toml"
    return
 cur_dir / file


class
 DBConfig(BaseModel):
    host: IPvAnyAddress = None
    port: int = Field(default=3306, gt=1000, lt=65535)
    name: str = None
    user: str = None
    pwd: str = None

    @computed_field

    @property

    def
 url(self) -> str:
        return
 f"{self.host}:{self.port}@{self.user}:{self.pwd}"


class
 MySettings(BaseSettings):
    APP_NAME: str = "MyAPP"
    LOG_LEVEL: str = ""
    DB_CONFIG: DBConfig = None
    DEBUG: bool = False

    model_config = SettingsConfigDict(
        toml_file=get_toml_file(),
        extra="ignore",
    )

    @classmethod

    def
 settings_customise_sources(
            cls,
            settings_cls: type[BaseSettings],
            init_settings: PydanticBaseSettingsSource,
            env_settings: PydanticBaseSettingsSource,
            dotenv_settings: PydanticBaseSettingsSource,
            file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
        return
 (
            init_settings,
            env_settings,
            TomlConfigSettingsSource(settings_cls),
            dotenv_settings,
            file_secret_settings,
        )

Settings = MySettings()

可以看到,利用toml文件的嵌套特性 (例如[DB_CONFIG]) 配置结构化对象比较简单。

总结

本质上是在不同的服务器中设置某个环境变量的不同值来加载不同的配置文件。此外,还要注意.gitignore 忽略掉密钥文件和.local文件。

网页链接:
https://wuguojing.cn/py/advanced/deploy.html#%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE

最新文章

随机文章