Руководство по переменным окружения для администратора
Основные методы работы с переменными окружения
Переменные окружения позволяют управлять конфигурацией приложения без изменения кода. В Python существует несколько способов их чтения и установки. Ниже рассматриваются наиболее распространённые подходы, их цели и возможные проблемы.
Эффективное решение: использование python-dotenv
Библиотека python-dotenv упрощает загрузку переменных из файла .env в os.environ. Это позволяет хранить чувствительные данные (токены, пароли) отдельно от кода и легко переключаться между окружениями (разработка, продакшн).
Как загрузить переменные из .env файла?
# Установка
# pip install python-dotenv
# .env файл в корне проекта
API_KEY=abc123
DATABASE_URL=postgresql://user:pass@localhost/db
Python настройки приложения (настройки приложения на python)
# main.py
import os
from dotenv import load_dotenv
# Загрузка .env (по умолчанию ищет .env в текущем каталоге)
load_dotenv()
api_key = os.getenv('API_KEY')
database_url = os.getenv('DATABASE_URL')
print(api_key) # abc123
print(database_url) # postgresql://user:pass@localhost/db
Python переменные окружения (переменные окружения в python)
Типичные ошибки:
- Файл .env не найден -
load_dotenv()вернётFalse, переменные останутся без изменений. Рекомендуется проверять возвращаемое значение и выводить предупреждение. - Переменные в .env переопределяют системные? По умолчанию
load_dotenv()не заменяет уже установленные переменные. Если нужно принудительно перезаписать, следует указатьload_dotenv(override=True). - Пробелы в значениях: значения обрезаются. Для сохранения пробелов значения следует заключать в кавычки в .env:
VAR=" value with spaces ".
Цель использования: отделить конфигурацию от кода, упростить развёртывание, избежать хранения ключей в репозитории.
Вариант 1: прямой доступ через os.environ
Модуль os предоставляет словарь environ, содержащий все текущие переменные окружения.
Как прочитать переменную окружения без сторонних библиотек?
import os
# Чтение (может вызвать KeyError)
path = os.environ['PATH']
print(path)
# Безопасное чтение с умолчанием
home = os.environ.get('HOME', '/tmp')
print(home)
# Установка новой переменной (действует только в текущем процессе)
os.environ['MY_CUSTOM_VAR'] = 'hello'
Path python (путь к python)
Ошибки и проблемы:
- Обращение к отсутствующему ключу -
KeyError. Следует всегда использовать методget()или проверять вхождение операторомin. - Значениями могут быть только строки. Попытка присвоить число приведёт к ошибке типа.
- Изменения через
os.environне сохраняются после завершения процесса.
Цель: быстрый доступ к системным переменным (PATH, USER), подходит для простых скриптов.
Вариант 2: функция os.getenv
Обёртка над os.environ.get().
import os
db_url = os.getenv('DATABASE_URL', 'sqlite:///default.db')
Python environment path (путь к окружению python)
Разница только в синтаксисе. Функция os.getenv - это синоним os.environ.get.
Ошибки: аналогичны os.environ. Не загружает .env автоматически.
Вариант 3: использование configparser для INI-файлов
Когда требуется структурированная конфигурация с секциями, удобно использовать configparser.
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
# Получение значения из секции DEFAULT
db_host = config['DEFAULT']['Host']
print(db_host)
# Безопасное получение с умолчанием
port = config['DEFAULT'].get('Port', '5432')
Python windows paths (работа с путями в python на windows)
Проблемы:
- Файл может отсутствовать -
read()вернёт пустой список. Рекомендуется проверять результатconfig.read(). - Кодировка: по умолчанию ожидается UTF-8, но можно указать параметр
encoding='utf-8'. - Не поддерживает переменные окружения напрямую, требуется их смешивание вручную.
Цель: сложные конфигурации с группировкой (например, разные настройки для БД, кэша).
Вариант 4: pydantic-settings с валидацией
Библиотека pydantic-settings (на базе Pydantic) позволяет описывать модель настроек с указанием типов, валидацией и автоматическим чтением из .env.
# pip install pydantic-settings
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = 'MyApp'
admin_email: str
secret_key: str
class Config:
env_file = '.env' # откуда загружать
env_file_encoding = 'utf-8'
# extra = 'ignore' # игнорировать лишние переменные
settings = Settings()
print(settings.admin_email)
Python файлы настроек (файлы конфигурации в python)
Ошибки:
- Если обязательная переменная не задана, Pydantic выбросит
ValidationError. - Требуется установка pydantic и pydantic-settings. Для простых проектов может быть избыточно.
Цель: типизированная, самодокументируемая конфигурация с проверкой на этапе запуска.
Вариант 5: комбинация с argparse для CLI
Иногда нужно, чтобы аргументы командной строки имели приоритет над переменными окружения.
import argparse
import os
parser = argparse.ArgumentParser()
parser.add_argument('--host', default=os.getenv('HOST', 'localhost'))
args = parser.parse_args()
print(f'Host: {args.host}')
Проблемы: легко запутаться в приоритетах. Следует чётко документировать, что имеет больший приоритет.
Цель: гибкие утилиты командной строки, которые можно настраивать и через CLI, и через окружение.
Пример 1: Загрузка окружения в зависимости от режима (development/production)
Использование разных .env файлов для разных сред. Файлы .env.development, .env.production. Загрузка с переменной окружения ENV.
import os
from dotenv import load_dotenv
env_mode = os.getenv('ENV', 'development')
env_file = f'.env.{env_mode}'
if not load_dotenv(env_file):
print(f'Предупреждение: файл {env_file} не найден')
# fallback на .env
load_dotenv('.env')
# Теперь переменные из соответствующего файла загружены
print(os.getenv('DATABASE_URL'))
# При запуске с ENV=production # postgresql://prod_user:prod_pass@prod_host/db
Пример 2: Чтение Docker secrets через переменные окружения
Docker монтирует секреты как файлы в /run/secrets/. Можно прочитать их и записать в os.environ для использования приложением.
import os
def load_docker_secrets(secrets_dir='/run/secrets'):
if not os.path.isdir(secrets_dir):
return
for secret_file in os.listdir(secrets_dir):
secret_path = os.path.join(secrets_dir, secret_file)
with open(secret_path, 'r') as f:
value = f.read().strip()
os.environ[secret_file.upper()] = value
load_docker_secrets()
# Теперь os.getenv('DB_PASSWORD') вернёт содержимое файла /run/secrets/db_password
Пример 3: Переопределение системных переменных с python-dotenv
По умолчанию dotenv не перезаписывает существующие переменные. Пример принудительной перезаписи.
# .env
MY_VAR=from_dotenv
import os
os.environ['MY_VAR'] = 'from_system'
from dotenv import load_dotenv
load_dotenv(override=True) # перезаписать системные значениями из .env
print(os.getenv('MY_VAR')) # from_dotenv
from_dotenv
Пример 4: Валидация с Pydantic и несколько источников
Модель может читать как из .env, так и из переменных окружения. Показывает, как обрабатывать ошибки валидации.
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import ValidationError
class DatabaseSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix='DB_', env_file='.env')
host: str = 'localhost'
port: int = 5432
user: str
password: str
try:
db_settings = DatabaseSettings()
except ValidationError as e:
print(f'Ошибка в конфигурации: {e}')
exit(1)
print(f'Connecting to {db_settings.user}@{db_settings.host}:{db_settings.port}')
# Если переменная DB_USER не задана # Ошибка в конфигурации: 1 validation error for DatabaseSettings # user: field required (type=value_error.missing)
Пример 5: Передача переменных окружения в подпроцесс
При запуске внешней программы через subprocess можно передать изменённое окружение.
import subprocess
import os
# Создаём новое окружение с дополнительными переменными
env = os.environ.copy()
env['MY_ENV_VAR'] = 'custom_value'
# Запускаем скрипт, который выводит переменную
result = subprocess.run(
['python', '-c', 'import os; print(os.getenv("MY_ENV_VAR"))'],
capture_output=True,
text=True,
env=env
)
print(result.stdout) # custom_value
custom_value
Пример 6: Использование os.environ для временной установки переменной в тестах
В модульных тестах часто нужно временно изменить переменную окружения. Для этого применяется контекстный менеджер unittest.mock.patch.dict.
import os
from unittest.mock import patch
def test_my_function():
with patch.dict(os.environ, {'MY_VAR': 'test_value'}):
# внутри блока MY_VAR = test_value
assert os.getenv('MY_VAR') == 'test_value'
# после выхода восстанавливается исходное значение
# Тест проходит