Работа с переменными окружения через .env в Python
Файлы окружения: основной подход с python-dotenv
Файлы окружения (environment files) обычно имеют расширение .env и хранят пары ключ-значение. Наиболее эффективное решение для работы с ними в Python - библиотека python-dotenv. Она загружает содержимое .env в переменные окружения процесса, после чего доступ к ним получается через os.environ.
Установка:
pip install python-dotenvввод программ на python (ввод данных в программе python)
Пример файла .env:
API_KEY=abc123
DATABASE_URL=postgres://user:pass@localhost/db
DEBUG=truePython file io (ввод-вывод файлов в python)
Загрузка в коде:
from dotenv import load_dotenv
import os
load_dotenv() # ищет .env в текущей и родительских директориях
api_key = os.getenv("API_KEY")
database_url = os.getenv("DATABASE_URL")
debug = os.getenv("DEBUG", "false")
print(f"API: {api_key}")
print(f"DB: {database_url}")
print(f"Debug: {debug}")
Python temp files (временные файлы в python)
API: abc123 DB: postgres://user:pass@localhost/db Debug: true
Python index files (индексация файлов в python)
Типичные проблемы и способы их решения:
Проблема: файл .env не найден. Решение: явно указать путь: load_dotenv('/path/to/.env') или проверить текущую рабочую директорию.
Проблема: переменная имеет значение, содержащее пробелы или кавычки. Решение: оборачивать значение в двойные или одинарные кавычки в .env: VAR="value with spaces".
Проблема: переменная уже установлена в окружении ОС, и загрузка из .env не должна её перезаписывать. Решение: по умолчанию load_dotenv(override=False) - не перезаписывает. Для принудительной замены использовать override=True.
Альтернативные подходы и их особенности
Как использовать переменные окружения без .env файла?
Самый простой способ - полагаться на переменные, установленные в операционной системе. Они попадают в os.environ автоматически. Такой подход применяется в контейнерах (Docker, Kubernetes) или на серверах.
import os
user = os.getenv("USER")
db_url = os.environ["DATABASE_URL"] # вызовет KeyError, если нет
print(user, db_url)File python class (класс для работы с файлами в python)
Проблема: отсутствие переменной вызывает исключение. Решение: использовать os.getenv() с значением по умолчанию или конструкцию try/except.
Проблема: неудобство тестирования - каждый раз приходится устанавливать переменные вручную. Решение: применять unittest.mock.patch.dict для имитации окружения в тестах.
Как хранить конфигурацию в INI-файлах с помощью configparser?
Библиотека configparser позволяет читать файлы в формате Windows INI (секции, ключи). Подходит для более структурированной конфигурации, но не для секретов (файл обычно не добавляют в .gitignore).
# config.ini
[DEFAULT]
server=localhost
port=5432
[database]
dbname=mydb
user=admin
password=secretPython file utf 8 (кодировка utf-8 для файлов в python)
from configparser import ConfigParser
config = ConfigParser()
config.read('config.ini')
server = config.get('DEFAULT', 'server')
dbname = config.get('database', 'dbname')
print(server, dbname)Python config files (конфигурационные файлы в python)
Проблема: чувствительные данные (пароли) хранятся в открытом виде. Решение: комбинировать с .env для паролей, а остальное - в INI.
Проблема: нет поддержки типов (все строки). Решение: вручную преобразовывать (int(), bool()) или использовать config.getint(), getboolean().
Как применять JSON или YAML для конфигурации окружения?
Файлы .json и .yaml дают структурированное хранение (вложенные объекты, списки). Для работы с ними используются стандартная библиотека json и сторонняя PyYAML.
# config.json
{
"database": {
"host": "localhost",
"port": 5432,
"dbname": "mydb"
},
"debug": false
}Python copy file (копирование файла в python)
import json
with open('config.json', 'r') as f:
config = json.load(f)
host = config['database']['host']
debug = config['debug']
print(host, debug)Python log file (логирование в файл в python)
Для YAML требуется установка:
pip install pyyamlPython file methods (методы работы с файлами в python)
import yaml
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
print(config['database']['host'])File models in python (модели файлов в python)
Проблема: безопасность - yaml.load() может выполнить произвольный код. Решение: всегда использовать yaml.safe_load().
Проблема: эти файлы не предназначены для секретов; их часто коммитят в репозиторий. Решение: добавлять их в .gitignore или использовать шаблоны и подстановку переменных через os.getenv().
Как загрузить конфигурацию с валидацией и типизацией через pydantic-settings?
Библиотека pydantic-settings (часть pydantic) читает переменные из .env и автоматически преобразует в указанные типы, проверяет обязательность и значения по умолчанию. Это современный подход для больших проектов.
pip install pydantic-settingsFile handle python (обработка файлов в python)
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "MyApp"
debug: bool = False
database_url: str
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
print(settings.app_name, settings.debug, settings.database_url)Python open file read (открытие файла для чтения в python)
Проблема: отсутствие обязательной переменной вызывает ошибку валидации. Решение: установить значение по умолчанию или сделать поле необязательным с Optional[str].
Проблема: сложность настройки для новичков. Решение: начать с python-dotenv и переходить на pydantic-settings по мере роста проекта.
Как использовать библиотеку environs для удобного чтения .env?
environs - ещё одна популярная обёртка над os.environ с поддержкой .env, типов, списков, вложенных переменных и кастомизации.
pip install environsPython file position (позиционирование в файле python)
from environs import Env
env = Env()
env.read_env() # читает .env
api_key = env.str("API_KEY")
debug = env.bool("DEBUG", default=False)
ports = env.list("PORTS", subcast=int)
print(api_key, debug, ports)
Проблема: неверный подтип (например, строка вместо числа). Решение: использовать subcast или env.int().
Проблема: переменная отсутствует и нет значения по умолчанию - выбрасывается исключение. Решение: явно указывать default или использовать env.get().
Продвинутые примеры работы с файлами окружения
Загрузка из нескольких .env файлов (для разных окружений)
В проекте можно иметь .env.development и .env.production. Загрузить их последовательно, с перезаписью:
from dotenv import load_dotenv
# Сначала базовый файл
load_dotenv('.env')
# Затем специфичный файл с переопределением
load_dotenv('.env.production', override=True)
import os
print(os.getenv('DEBUG')) # будет false, если в .env.production указано DEBUG=false
Использование в FastAPI с pydantic-settings и автозагрузкой
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
app_name: str = "FastAPI App"
database_url: str
secret_key: str
max_connections: int = 10
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@lru_cache
def get_settings():
return Settings()
# Использование в роутере:
# from app.config import get_settings
# settings = get_settings()
# print(settings.database_url)
(на экран не выводится, переменные доступны в приложении)
Динамическое создание .env в Docker-контейнере
В Docker Compose можно передавать переменные через environment или файл env_file. В Python их читать через os.getenv (без явного вызова load_dotenv).
# docker-compose.yml
services:
app:
build: .
env_file:
- .env.production
# В Python-коде контейнера
import os
print(os.getenv('DATABASE_URL')) # значение из .env.production
Шифрование секретов в .env через переменную окружения
Можно хранить зашифрованное значение, а расшифровывать в коде с помощью ключа из системы:
from cryptography.fernet import Fernet
import os
from dotenv import load_dotenv
load_dotenv()
# Ключ шифрования хранится в системе (не в .env)
encryption_key = os.environ.get('ENCRYPTION_KEY')
if not encryption_key:
raise ValueError('ENCRYPTION_KEY not set')
cipher = Fernet(encryption_key.encode())
encrypted_db_url = os.getenv('ENCRYPTED_DATABASE_URL')
db_url = cipher.decrypt(encrypted_db_url.encode()).decode()
print(f'Decrypted DB URL: {db_url}')
Поиск .env в родительских директориях (для проектов с вложенными модулями)
По умолчанию load_dotenv() ищет файл от текущей рабочей директории вверх. Можно настроить поиск:
from dotenv import load_dotenv, find_dotenv
# Найти .env начиная от текущего файла
env_path = find_dotenv(usecwd=False)
if env_path:
load_dotenv(env_path)
else:
print('.env не найден')
Ленивая загрузка (lazy loading) при каждом обращении
Использование dotenv_values() для получения словаря без изменения os.environ:
from dotenv import dotenv_values
config = dotenv_values('.env.custom_name')
print(config.get('SECRET')) # None, если нет
# При этом os.environ не затрагивается
Обработка отсутствующих переменных с помощью пользовательского исключения
from dotenv import load_dotenv
import os
load_dotenv()
required = ['API_KEY', 'DATABASE_URL']
missing = [var for var in required if not os.getenv(var)]
if missing:
raise EnvironmentError(f'Missing required environment variables: {missing}')
Использование в тестах с подменой окружения
import os
from unittest.mock import patch
@patch.dict(os.environ, {'MY_VAR': 'test_value'})
def test_my_var():
print(os.getenv('MY_VAR')) # test_value
test_my_var()
print(os.getenv('MY_VAR')) # None (после теста восстановлено)
test_value None