Переменные окружения для Python проектов


При разработки web-приложения или бота мы часто имеем дело с какой-либо секретной информацией, различными токенами и паролями (API-ключами, секретами веб-форм). "Хардкодить" эту информацию, а тем более сохранять в публично доступной системе контроля версий это очень плохая идея.


# Плохая практика. Не делай так.
API_KEY = 'very_secret_password'

Конфигурационные файлы


Самый простой путь решения данной проблемы, это создание отдельного конфигурационного файла со всей чувствительной информацией и добавление его в .gitignore. Минус такого подхода в том, что в гит нужно держать ещё и шаблон конфигурационного файла и не забывать его периодически обновлять.


# Уже лучше.
from config import API_KEY
app = Flask(__name__)
app.config['API_KEY'] = API_KEY

Переменные окружения


Более продвинутый подход, это использование переменных окружения. Переменные окружения это именованные переменные, содержащие текстовую информацию, которую могут использовать запускаемые программы. Например, чтобы запустить flask-приложение, вначале нужно указать в переменной окружения FLASK_APP имя нашего приложения:


$ export FLASK_APP=hello.py
$ flask run
 * Running on http://127.0.0.1:5000/

С помощью переменных окружения можно получать различные параметры приложение и секретные ключи:


import os
app.config['API_KEY'] = os.environ.get('API_KEY')

Библиотека python-dotenv


Чтобы не задавать каждый раз вручную переменные окружения при новом запуске терминала, можно воспользоваться пакетом python-dotenv. Он позволяет загружать переменные окружения из файла .env в корневом каталоге приложения.
Устанавливаем пакет:


pip install python-dotenv

Теперь можно создать файл .env со всеми переменными среды, которые необходимы вашему приложению. Важно, добавьте .env-файл в .gitignore, не храните его в системе контроля версий.


import os
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(dotenv_path):
    load_dotenv(dotenv_path)

Этот .env-файл можно использовать для всех переменных конфигурации, но его нельзя использовать для переменных среды FLASK_APP и FLASK_DEBUG, так как они необходимы уже в процессе начальной загрузки приложения.


Утилита direnv


Переменные среды могут быть автоматически загружены при входе в папку с проектом, это особенно удобно при работе с несколькими проектами одновременно. Сделать это позволяет утилита direnv. Direnv — это менеджер переменных среды для терминала, поддерживает bash, zsh, tcsh и др. оболочки. Позволяет автоматически загружать и выгружать переменные среды в зависимости от вашего текущего каталога. Это позволяет иметь переменные среды, специфичные для каждого проекта. Перед каждым приглашением проверяется наличие файла .envrc в текущем и родительском каталогах. Если файл существует, он загружается в подшаблон bash, и все экспортированные переменные затем захватываются direnv, а затем становятся доступными для оболочки.


Установка direnv


sudo apt-get install direnv

Далее необходимо в нести изменения для настройки нашей оболочки, для bash необходимо в конец файла ~/.bashrc добавить следующее и перезапустить консоль:


eval "$(direnv hook bash)"

Пример использования direnv

Создадим новую папку для проекта:


$ mkdir ~/my-project
$ cd ~/my-project

Покажем, что переменная окружения FLASK_APP не загружена:


$ echo $FLASK_APP
nope

Запишем переменные окружения в файл .envrc:


$ echo export FLASK_APP=hello.py > .envrc
.envrc is not allowed

Для обеспечения безопасности, после создания или изменения файла .envrc, нужно выполнить подтверждение с помощью команды direnv allow:


$ direnv allow .
direnv: reloading
direnv: loading .envrc
direnv export: +FLASK_APP

Покажем, что переменная окружения загружена:


$ echo $FLASK_APP
hello.py

При выхода из папки с проектом переменные окружения выгружаются


$ cd ..
direnv: unloading

и становятся снова не заданными


$ echo $FLASK_APP
nope

Работа с виртуальным окружением в direnv


Кроме загрузки переменных окружения, утилита direnv позволяет также работать с виртуальным окружением для Python.
Виртуальное окружение позволяет использовать для отдельные проектов разные версии интерпретатора python и пакетов библиотек. Существует несколько способов создания виртуального окружения для python, здесь мы рассмотрим модуль venv, для другие варианты описаны в документации к direnv.


Чтобы использовать venv для автоматического создания и активирования виртуального окружения, необходимо добавить в файл ~/.config/direnv/direnvrc следующий код (см. документацию).


Создание виртуального окружения

Если в файл .envrc добавить строчку


layout python-venv

то при переходе в папку будет direnv создаст виртуальное окружение в папке direnv, например .direnv/python-venv-3.7.3.
Чтобы создать виртуальное окружение с другим путем, например в более привычной папке venv, надо задать переменную VIRTUAL_ENV:


export VIRTUAL_ENV=.venv

Таким же способом можно подключать уже созданное виртуальное окружение.


Работа с разными версиями Python

Для установки отличной от системной версии python, нужно использовать команду:


layout python-venv python3.6

Создаем строку приглашения bash (PS1)

В отличие от ручной активации виртуального окружения, в нашем случае строка приглашения bash (PS1) не будет изменена (обычно она выглядит как (venv) user@comp:~$ ). Чтобы вернуть показ активации виртуального окружения в консоли нужно в файл ~/.bashrc добавить следующий код:


show_virtual_env() {
  if [[ -n "$VIRTUAL_ENV" && -n "$DIRENV_DIR" ]]; then
    echo "($(basename $VIRTUAL_ENV))"
  fi
}
export -f show_virtual_env
PS1='$(show_virtual_env)'$PS1

Пример файла настройки файла .envrc

Вот так может выглядеть файл .envrc настроенный для разработки flask-приложения:


export VIRTUAL_ENV=venv
layout python-venv
export FLASK_APP=app.py
export FLASK_DEBUG=1

Это позволяет автоматически активировать виртуальное окружение и загрузить переменные окружения при входе в папку с проектом.


terminal_direnv

Комментарии (11)


  1. gnomeby
    23.10.2019 13:12

    Насколько я помню апач при запуске с запароленным сертификатом спрашивает пароль интерактивно прямо во время старта. То есть даже в некоторых местах переменным окружения не доверяют.


  1. TSchmidt
    23.10.2019 13:41

    .gitignor

    Всё таки ".gitignore", мне кажется.


    1. AndreyPhys Автор
      23.10.2019 13:47

      Точно.
      Спасибо, исправил.


  1. webdevium
    23.10.2019 13:49

    Стоит обратить внимание на dotenv-linter


  1. mrBuG
    23.10.2019 13:58

    Есть ещё один вариант — управлять виртуальным окружением через pipenv. Оно уже из коробки поддерживает .env файлы и выставляет оттуда переменные окружения при выполнении команд pipenv shell / pipenv run. Остается лишь обратиться к ним из проекта любым удобным способом.


  1. kostyaten
    23.10.2019 14:05

    Вопрос, чем плохо иметь в /etc/environment
    export KEY=«Ключ»
    И в питоне получать os.environ.get('KEY') ???


    1. AndreyPhys Автор
      23.10.2019 14:19

      Хранить переменные окружения в /etc/environment плохо тем, что это требует наличия прав суперпользователя и при таком подходе трудно разделять переменные окружения для разных проектов.
      Кроме того, иногда нужно задавать переменные окружения до запуска питона.


  1. eternal_why
    23.10.2019 20:50

    Мне единственному кажется странной идея использовать библиотеки для загрузки в окружение нежелательных к сохранению в файлы переменных из, опаньки, сохранённых файлов?.. :)


  1. dmytrohoi
    24.10.2019 02:37

    Для полноты картины стоит упомянуть virtualenvwrapper под virtualenv и, как указал mrBug, мощную Pipenv.


    Приходилось работать с последней, легко начать работу и оперировать зависимостями, ресурсами и т.д.


  1. baldr
    25.10.2019 17:30

    Да, действительно, наблюдается некоторая непоследовательность:
    Хранить параметры в файле плохо, поэтому давайте будем хранить в переменных окружения, которые запишем в файл. И для этого нам еще нужны библиотеки сторонние!

    Вдобавок, если нам потребуется, например, группировать переменные, то, простите — только плоская структура:
    AWS_ACCESS_KEY/AWS_SECRET_KEY — это еще просто.

    Как вы, например, зададите параметры подключения к базам для Django (settings.DATABASES)?

    Я лично использую YAML-файлики, которые перезаписывают все дефолтные переменные. Файлы храню в /usr/local/etc — но это уже предпочтения.

    Очень не рекомендую хранить в папке с проектом ничего локально-зависимого. Пример: для отладки я очень часто синхронизирую код в папке с проектом на удаленный сервер с помощью rsync (lsyncd). Если там лежит какой-нибудь local_settings.yaml для моей локальной системы — он тоже будет отправлен на сервер, хотя там будет нужен свой. Ну а сколько историй о том, как кто-то по ошибке закоммитил локальный конфиг…


    1. webdevium
      25.10.2019 19:08
      +2

      Историй много таких, конечно. Потому и додумались, что в проекте есть только .env.example (любое другое имя), как набор возможных параметров и примеры их наполнения. А вот нужный "локальный" .env обязательно в .gitignore. Естественно, этот файл никогда не попадет на сервер, пока вы не начнете использовать для деплоя НЕ git.
      Но даже для Вашего случая с rsync есть отличное решение:


      rsync --filter=":- .gitignore" ...