Search code examples
pythonpython-typing

Cast and type env variables using file


For all my projects, I load all env variables at the start and check that all the expected keys exist as described by an .env.example file following the dotenv-safe approach.

However, the env variables are strings, which have to be manually cast whenever they're used inside the Python code. This is annoying and error-prone. I'd like to use the information from the .env.example file to cast the env variables and get Python typing support in my IDE (VS Code). How do I do that?

env.example

PORT: int
SSL: boolean

Python Ideal Behavior

# Set the env in some way (doesn't matter)
import os
os.environment["SSL"] = "0"
os.environment["PORT"] = "99999"

env = type_env()
if not env["SSL"]: # <-- I'd like this to be cast to boolean and typed as a boolean
    print("Connecting w/o SSL!")
if 65535 < env["PORT"]:  # <-- I'd like this to be cast to int and typed as an int
    print("Invalid port!")

In this code example, what would the type_env() function look like assuming it only supported boolean, int, float, and str?

It's not too hard to do the casting as shown in e.g. https://stackoverflow.com/a/11781375/1452257, but it's unclear to me how to get it working with typing support.


Solution

  • I will suggest using pydantic.

    From StackOverflow pydantic tag info

    Pydantic is a library for data validation and settings management based on Python type hinting (PEP484) and variable annotations (PEP526). It allows for defining schemas in Python for complex structures.

    let's assume that you have a file with your SSL and PORT envs:

    with open('.env', 'w') as fp:
        fp.write('PORT=5000\nSSL=0')
    

    then you can use:

    from pydantic import BaseSettings
    
    class Settings(BaseSettings):
        PORT : int
        SSL : bool
        class Config:
            env_file = '.env'
    
    config = Settings()
    
    print(type(config.SSL),  config.SSL)
    print(type(config.PORT),  config.PORT)
    # <class 'bool'> False
    # <class 'int'> 5000
    

    with your code:

    env = Settings()
    
    if not env.SSL:
        print("Connecting w/o SSL!")
    if 65535 < env.PORT: 
        print("Invalid port!")
    

    output:

    Connecting w/o SSL!