关于Flask通过.env加载环境变量的两个坑


#1

其实本质都是python-dotenv加载环境变量出现的问题。

坑一:python-dotenv加载的Value都是字符串类型

第一个坑是python-dotenv加载的Value都是字符串类型python-dotenv版本0.10.1),因此导致整型、浮点型和布尔类型需要转换一下。

解决方案

目前解决办法只能是这样:

# `.env`
MAIL_PORT = 465
MAIL_USE_SSL = false
MAIL_USE_TLS = true
# settings.py
class BaseConfig(object):
    ...
    MAIL_PORT = int(os.getenv('MAIL_PORT', default=587))
    MAIL_USE_SSL = True if 'true' == os.getenv('MAIL_USE_SSL') else False
    MAIL_USE_TLS = True if 'true' == os.getenv('MAIL_USE_TLS') else False

相关帖子:

坑二:pipenv影响了flask加载.env环境变量

第二个坑和pipenv有关,众所周知Flask项目可以通过.env加载环境变量,但是,pipenv也可以通过.env加载环境变量!问题就出现了,进入pipenv shell虚拟环境后,修改.env环境变量后再启动Flask app:flask run,Flask还是用了原来的环境变量!!!

究其原因,是pipenv shell加载了环境变量并进行了缓存,然后flask加载环境变量时没有进行覆盖

> pipenv shell
Loading .env environment variables…
Launching subshell in virtual environment…
...
> flask run  # 正常预期
# 停止flask,修改 .env 环境变量,保存
> flask run  # 没达到修改变量后的预期效果

尤其是部署Flask到服务器后,以下步骤肯定有问题的!!!

$ pipenv shell
$ vim .env
...
$ flask run

解决方案

方案一:重进pipenv shell

一种解决方案就是退出pipenv shell环境再进入:

> pipenv shell
> # 编辑 .env
...
> exit
> pipenv shell

方案二:新建并使用app.py启动

另一个解决方案是在主目录下新建一个app.py,拷贝下面代码,以后使用python app.py启动。

# coding=utf-8
"""
python app.py
"""

import os
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), '.env')
if os.path.exists(dotenv_path):
    load_dotenv(dotenv_path, override=True)  #  override=True: 覆写已存在的变量

from apps.web import create_app

app = create_app()

if __name__ == "__main__":
    app.run()

这种代码有时也会给一些wsgi(比如gunicorn)提供。

方案三:设置PIPENV_DONT_LOAD_ENV=1

还有一个方案是设置PIPENV_DONT_LOAD_ENV=1,不让pipenv加载.env

PowerShell示例(注意没有了Loading .env environment variables…信息):

> $env:PIPENV_DONT_LOAD_ENV=1
> pipenv shell
Launching subshell in virtual environment…
Windows PowerShell
...

方案四:使用.flaskenv

pipenv shell不会从.flaskenv加载变量,所以如果有经常需要修改的环境变量也可以放在.flaskenv。但是我感觉一点也不优雅,因为我习惯把.flaskenv也提交到仓库,而留下.env在部署端客制化。

另外,对于这个坑我还找到一个彩蛋,李辉老师已经在17年提过类似的Issue了哈哈。

其实我认为flask加载时,覆写python-dotenv环境变量比较好。


#2

这个话题有不少想法,对 Flask 目前的实现也不是很满意。今天比较忙,先简单提一点,有空再补充。

按照我的经验,像你给出的这几个配置选项是不需要放到 .env 和 .flaskenv 文件的,直接写到配置脚本(比如 config.py / settings.py)就可以了。类似的,其他的大部分配置也都不需要。这两个文件通常写入的内容如下:

.env:不能公开的敏感数据,比如:

  • 密钥
  • 数据库 URL
  • 邮件服务器或其他第三方服务的密码 / 密钥 / 令牌值

.flaskenv:和 Flask 开发服务器相关的几个环境变量,比如:

  • FLASK_ENV
  • FLASK_APP
  • FLASK_DEBUG
  • FLASK_RUN_PORT 等

另外,对于环境变量,无论是使用 python-dotenv 等工具写入,还是手动使用 set / export 命令,最终在 Python 里获取的时候都会是字符串类型。


#3

恩。为什么我考虑要把一些配置放在.env呢?因为我这边要考虑可能会把代码交付给实施/运维工程师的情况,如果我有把配置选项都引出来,那么我只要告诉他只需要编辑.env(类似于.conf)就可以修改相关配置了,而不需要去项目目录里找config.pysetting.py修改。

所以我有时总是在思考一个和Flask相性良好的,能把配置引出来的解决方案,目前仅仅是借用了.env当作.conf

和Flask相性良好——意思是说,比如我用ini引出配置选项也没问题,但总显得多余繁冗,还需要写相关加载配置的代码等等。也许接下来,我应该考虑下Flask的实例文件夹了哈哈~


There should be one-- and preferably only one --obvious way to do it.


#4

而「从环境变量加载变量值都是字符串」这个feature(居然不是bug),我相信绝对能坑很多新手,所以写出来提醒一下哈哈。


#5

能把配置引出来的解决方案

除了实例文件夹,或许可以在项目根目录放一个配置文件,写两个导入语句,后导入这个文件,覆盖第一次导入的配置(仅为设想,未测试):

app.config.from_pyfile('settings.py')
app.config.from_pyfile('global_settings.py') # 这个文件的配置覆盖上面的配置文件中对应的配置