zoukankan      html  css  js  c++  java
  • python之environs

      下面推荐一个 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')
  • 相关阅读:
    基于ExtAspNet的开源项目 Ext4JSLint
    ExtAspNet应用技巧(九) Grid导出为Excel文件
    ExtAspNet应用技巧(十二) 系统登录
    ExtAspNet v2.0.7
    原来最可怕的不是工作,是无聊
    获取Word文档的作者和主题
    《可变范围规约》
    用IronPython加载,写入文本文件
    IronPython中没有System.Data命名空间?
    《敏捷建模》读后感
  • 原文地址:https://www.cnblogs.com/xingxia/p/python_environs.html
Copyright © 2011-2022 走看看