Работа с переменными окружения через .env в Python

Раздел: 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=true

Python 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=secret

Python 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 pyyaml

Python 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-settings

File 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 environs

Python 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().

- Python работа с данными файла (работа с данными из файла в python)
- Key files python (работа с ключевыми файлами в python)
- Python file w (режим записи в файл в python)

Продвинутые примеры работы с файлами окружения

Загрузка из нескольких .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

Файлы окружения в Python - comments

En
Python environment file (python)