下面推荐一个 environs 库,利用它我们可以轻松地设置各种类型的环境变量。

安装:

  

pip3 install environs

好,安装之后,我们再来体验一下使用 environs 来设置环境变量的方式。

from environs import Env

env = Env()
VAR1 = env.int('VAR1', 1)
VAR2 = env.float('VAR2', 5.5)
VAR3 = env.list('VAR3')

这里 environs 直接提供了 int、float、list 等方法,我们就不用再去进行类型转换了。

与此同时,设置环境变量的方式也有所变化:

export VAR1=1
export VAR2=2.3
export VAR3=1,2

这里 VAR3 是列表,我们可以直接用逗号分隔开来。

打印结果如下:

1
2.3
['1', '2']

下面我们再看一个官方示例,这里示例了一些常见的用法。

首先我们来定义一些环境变量,如下:

export GITHUB_USER=sloria
export MAX_CONNECTIONS=100
export SHIP_DATE='1984-06-25'
export TTL=42
export ENABLE_LOGIN=true
export GITHUB_REPOS=webargs,konch,ped
export COORDINATES=23.3,50.0
export LOG_LEVEL=DEBUG

这里有字符串、有日期、有日志级别、有字符串列表、有浮点数列表、有布尔。

我们来看下怎么获取,写法如下:

from environs import Env

env = Env()
env.read_env() # read .env file, if it exists
# required variables
gh_user = env("GITHUB_USER") # => 'sloria'
secret = env("SECRET") # => raises error if not set # casting
max_connections = env.int("MAX_CONNECTIONS") # => 100
ship_date = env.date("SHIP_DATE") # => datetime.date(1984, 6, 25)
ttl = env.timedelta("TTL") # => datetime.timedelta(0, 42)
log_level = env.log_level("LOG_LEVEL") # => logging.DEBUG # providing a default value
enable_login = env.bool("ENABLE_LOGIN", False) # => True
enable_feature_x = env.bool("ENABLE_FEATURE_X", False) # => False # parsing lists
gh_repos = env.list("GITHUB_REPOS") # => ['webargs', 'konch', 'ped']
coords = env.list("COORDINATES", subcast=float) # => [23.3, 50.0]

通过观察代码可以发现它提供了这些功能:

  • 通过 env 可以设置必需定义的变量,如果没有定义,则会报错。
  • 通过 date、timedelta 方法可以对日期或时间进行转化,转成 datetime.date 或 timedelta 类型。
  • 通过 log_level 方法可以对日志级别进行转化,转成 logging 里的日志级别定义。
  • 通过 bool 方法可以对布尔类型变量进行转化。
  • 通过 list 方法可以对逗号分隔的内容进行 list 转化,并可以通过 subcast 方法对 list 的每个元素进行类型转化。

可以说有了这些方法,定义各种类型的变量都不再是问题了。

支持类型

总的来说,environs 支持的转化类型有这么多:

  • env.str
  • env.bool
  • env.int
  • env.float
  • env.decimal
  • env.list (accepts optional subcast keyword argument)
  • env.dict (accepts optional subcast keyword argument)
  • env.json
  • env.datetime
  • env.date
  • env.timedelta (assumes value is an integer in seconds)
  • env.url
  • env.uuid
  • env.log_level
  • env.path (casts to a pathlib.Path)

这里 list、dict、json、date、url、uuid、path 个人认为都还是比较有用的,另外 list、dict 方法还有一个 subcast 方法可以对元素内容进行转化。

对于 dict、url、date、uuid、path 这里我们来补充说明一下。

下面我们定义这些类型的环境变量:

export VAR_DICT=name=germey,age=25
export VAR_JSON='{"name": "germey", "age": 25}'
export VAR_URL=https://cuiqingcai.com
export VAR_UUID=762c8d53-5860-4d5d-81bc-210bf2663d0e
export VAR_PATH=/var/py/env

需要注意的是,DICT 的解析,需要传入的是逗号分隔的键值对,JSON 的解析是需要传入序列化的字符串。

解析写法如下:

from environs import Env

env = Env()
VAR_DICT = env.dict('VAR_DICT')
print(type(VAR_DICT), VAR_DICT) VAR_JSON = env.json('VAR_JSON')
print(type(VAR_JSON), VAR_JSON) VAR_URL = env.url('VAR_URL')
print(type(VAR_URL), VAR_URL) VAR_UUID = env.uuid('VAR_UUID')
print(type(VAR_UUID), VAR_UUID) VAR_PATH = env.path('VAR_PATH')
print(type(VAR_PATH), VAR_PATH) 运行结果:
<class 'dict'> {'name': 'germey', 'age': '25'}
<class 'dict'> {'name': 'germey', 'age': 25}
<class 'urllib.parse.ParseResult'> ParseResult(scheme='https', netloc='cuiqingcai.com', path='', params='', query='', fragment='')
<class 'uuid.UUID'> 762c8d53-5860-4d5d-81bc-210bf2663d0e
<class 'pathlib.PosixPath'> /var/py/env

可以看到,它分别给我们转化成了 dict、dict、ParseResult、UUID、PosixPath 类型了。

在代码中直接使用即可。

文件读取

如果我们的一些环境变量是定义在文件中的,environs 还可以进行读取和加载,默认会读取本地当前运行目录下的 .env 文件。

示例如下:

from environs import Env

env = Env()
env.read_env()
APP_DEBUG = env.bool('APP_DEBUG')
APP_ENV = env.str('APP_ENV')
print(APP_DEBUG)
print(APP_ENV) 下面我们在 .env 文件中写入如下内容:
APP_DEBUG=false
APP_ENV=prod 运行结果:
False
prod

当然我们也可以自定义读取的文件,如 .env.test 文件,内容如下:

APP_DEBUG=false
APP_ENV=test 代码调整:
from environs import Env env = Env()
env.read_env(path='.env.test')
APP_DEBUG = env.bool('APP_DEBUG')
APP_ENV = env.str('APP_ENV')

这里就通过 path 传入了定义环境变量的文件路径即可。

前缀处理

environs 还支持前缀处理,一般来说我们定义一些环境变量,如数据库的连接,可能有 host、port、password 等,但在定义环境变量的时候往往会加上对应的前缀,如 MYSQL_HOST、MYSQL_PORT、MYSQL_PASSWORD 等,但在解析时,我们可以根据前缀进行分组处理,见下面的示例:

# export MYAPP_HOST=lolcathost
# export MYAPP_PORT=3000 with env.prefixed("MYAPP_"):
host = env("HOST", "localhost") # => 'lolcathost'
port = env.int("PORT", 5000) # => 3000 # nested prefixes are also supported: # export MYAPP_DB_HOST=lolcathost
# export MYAPP_DB_PORT=10101 with env.prefixed("MYAPP_"):
with env.prefixed("DB_"):
db_host = env("HOST", "lolcathost")
db_port = env.int("PORT", 10101)

可以看到这里通过 with 和 priefixed 方法组合使用即可实现分区处理,这样在每个分组下再赋值到一个字典里面即可。

合法性验证

有些环境变量的传入是不可预知的,如果传入一些非法的环境变量很可能导致一些难以预料的问题。比如说一些可执行的命令,通过环境变量传进来,如果是危险命令,那么会非常危险。

所以在某些情况下我们需要验证传入的环境变量的有效性,看下面的例子:

# export TTL=-2
# export NODE_ENV='invalid'
# export EMAIL='^_^' from environs import Env
from marshmallow.validate import OneOf, Length, Email env = Env() # simple validator
env.int("TTL", validate=lambda n: n > 0)
# => Environment variable "TTL" invalid: ['Invalid value.'] # using marshmallow validators
env.str(
"NODE_ENV",
validate=OneOf(
["production", "development"], error="NODE_ENV must be one of: {choices}"
),
)
# => Environment variable "NODE_ENV" invalid: ['NODE_ENV must be one of: production, development'] # multiple validators
env.str("EMAIL", validate=[Length(min=4), Email()])
# => Environment variable "EMAIL" invalid: ['Shorter than minimum length 4.', 'Not a valid email address.']

在这里,我们通过 validate 方法,并传入一些判断条件。如 NODE_ENV 只允许传入 production 和 develpment 其中之一;EMAIL 必须符合 email 的格式。

这里依赖于 marshmallow 这个库,里面有很多验证条件,大家可以了解下。

如果不符合条件的,会直接抛错,例如:

marshmallow.exceptions.ValidationError: ['Invalid value.']

关于 marshmallow 库的用法,大家可以参考:https://marshmallow.readthedocs.io/en/stable/

最后再附一点我平时定义环境变量的一些常见写法,如:

import platform
from os.path import dirname, abspath, join
from environs import Env
from loguru import logger env = Env()
env.read_env() # definition of flags
IS_WINDOWS = platform.system().lower() == 'windows' # definition of dirs
ROOT_DIR = dirname(dirname(abspath(__file__)))
LOG_DIR = join(ROOT_DIR, env.str('LOG_DIR', 'logs')) # definition of environments
DEV_MODE, TEST_MODE, PROD_MODE = 'dev', 'test', 'prod'
APP_ENV = env.str('APP_ENV', DEV_MODE).lower()
APP_DEBUG = env.bool('APP_DEBUG', True if APP_ENV == DEV_MODE else False)
APP_DEV = IS_DEV = APP_ENV == DEV_MODE
APP_PROD = IS_PROD = APP_DEV == PROD_MODE
APP_TEST = IS_TEST = APP_ENV = TEST_MODE # redis host
REDIS_HOST = env.str('REDIS_HOST', '127.0.0.1')
# redis port
REDIS_PORT = env.int('REDIS_PORT', 6379)
# redis password, if no password, set it to None
REDIS_PASSWORD = env.str('REDIS_PASSWORD', None)
# redis connection string, like redis://[password]@host:port or rediss://[password]@host:port
REDIS_CONNECTION_STRING = env.str('REDIS_CONNECTION_STRING', None) # definition of api
API_HOST = env.str('API_HOST', '0.0.0.0')
API_PORT = env.int('API_PORT', 5555)
API_THREADED = env.bool('API_THREADED', True) # definition of flags
ENABLE_TESTER = env.bool('ENABLE_TESTER', True)
ENABLE_GETTER = env.bool('ENABLE_GETTER', True)
ENABLE_SERVER = env.bool('ENABLE_SERVER', True) # logger
logger.add(env.str('LOG_RUNTIME_FILE', 'runtime.log'), level='DEBUG', rotation='1 week', retention='20 days')
logger.add(env.str('LOG_ERROR_FILE', 'error.log'), level='ERROR', rotation='1 week')

python之environs的更多相关文章

  1. python webdriver 测试框架-行为驱动例子

    安装行为驱动模块lettuce(卷心菜)模块 pip install lettuce Successfully installed argparse-1.4.0 colorama-0.3.9 extr ...

  2. python 面试真题

    0.Python是什么? Python是一种解释型语言.但是跟C和C的衍生语言不同,Python代码在运行之前不需要编译.其他解释型语言还包括PHP和Ruby. Python是动态类型语言,指的是在声 ...

  3. Python中的多进程与多线程(一)

    一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...

  4. Python高手之路【六】python基础之字符串格式化

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...

  5. Python 小而美的函数

    python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况   any any(iterable) ...

  6. JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议

    软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...

  7. 可爱的豆子——使用Beans思想让Python代码更易维护

    title: 可爱的豆子--使用Beans思想让Python代码更易维护 toc: false comments: true date: 2016-06-19 21:43:33 tags: [Pyth ...

  8. 使用Python保存屏幕截图(不使用PIL)

    起因 在极客学院讲授<使用Python编写远程控制程序>的课程中,涉及到查看被控制电脑屏幕截图的功能. 如果使用PIL,这个需求只需要三行代码: from PIL import Image ...

  9. Python编码记录

    字节流和字符串 当使用Python定义一个字符串时,实际会存储一个字节串: "abc"--[97][98][99] python2.x默认会把所有的字符串当做ASCII码来对待,但 ...

  10. Apache执行Python脚本

    由于经常需要到服务器上执行些命令,有些命令懒得敲,就准备写点脚本直接浏览器调用就好了,比如这样: 因为线上有现成的Apache,就直接放它里面了,当然访问安全要设置,我似乎别的随笔里写了安全问题,这里 ...

随机推荐

  1. 小忙半个月搞个CKA,可还行,搞完后发现自己被割韭菜了

  2. react 中获取子组件

    wrappedComponentRef={(form) => this.table = form}

  3. 鸿蒙系统(HarmonyOS)全局弹窗实现

    全局弹窗相对于自定义弹窗有以下优点: 封装更彻底,一行代码就能调用 跟组件耦合度低,只需要传入组件的UIContext对象,不需要跟自定义弹窗一样需要在组件内部实例化CustomDialogContr ...

  4. sharepoint 打开rdl报表,报表服务器数据库内出错。此错误可能是因连接失败、超时或数据库中磁盘空间不足而导致的

    最近在做reporting services报表的时候,部署到sharepoint后,打开rdl报表,经常遇到一个问题: 报表服务器数据库内出错.此错误可能是因连接失败.超时或数据库中磁盘空间不足而导 ...

  5. 使用 Nuxt Kit 检查模块与 Nuxt 版本兼容性

    title: 使用 Nuxt Kit 检查模块与 Nuxt 版本兼容性 date: 2024/9/13 updated: 2024/9/13 author: cmdragon excerpt: 通过 ...

  6. RxJS 系列 – Scheduler

    前言 大部分情况下, RxJS 都是用来处理异步执行的. 比如 Ajax, EventListener 等等. 但其实, 它也是可以同步执行的, 甚至 by default 它就是同步执行的 (下面会 ...

  7. ASP.NET Core – MVC

    前言 在 ASP.NET Core – MVC vs Razor Page 里有提到 MVC. 它算是 WebAPI 的抽象. 但是通常 MVC 指的是比较传统的 Website, WebAPI 则是 ...

  8. 5G网元功能与接口

    5G网元功能与接口 5G移动通信整体网络架构 5G网络功能之间的信息交互可以基于两种方式表示:其一为基于服务表示:其二为基于点对点表示.实时部署时,也可以采用两种方式相结合的表示方式. 并不是所有的接 ...

  9. [OI] 容斥原理拓展

    10.容斥原理拓展 10.1 二项式反演 \[P.10.1(1) \] 设 \(U=\{S_1,S_2,S_3...S_n\}\),且任意 \(i\) 个元素的交集都相等 定义 \(g(x)\) 为 ...

  10. [namespace hdk] 64位 bitset

    功能 已重载运算符 [](int) (右值,修改请使用 set() 方法) ~() +(bitset) +(unsigned long long) +=(bitset) +=(unsigned lon ...