Skip to content

ewen-lbh/python-typed-dotenv

Repository files navigation

typed_dotenv

Parse .env files with types

Installation

pip install typed_dotenv

To use...

  • load_into: pip install typed_dotenv[pydantic]
  • YAML literals: pip install typed_dotenv[yaml]
  • TOML literals: pip install typed_dotenv[toml]

Usage

With the following .env file:

GITHUB_TOKEN="jkjkimnotputtinmygithubpersonaltokeninamodulesexamples"
DEBUG_IN_PRODUCTION=False
import typed_dotenv

secrets = typed_dotenv.load(".env")

You'll see here that nothing has changed much: secrets['DEBUG_IN_PRODUCTION'] is still a str.

That's because you need to explicitly define what syntax your .env uses. Add the following at the top of your .env:

# values: python
GITHUB_TOKEN="jkjkimnotputtinmygithubpersonaltokeninamodulesexamples"
DEBUG_IN_PRODUCTION=False

Now the following will not raise an assertion error:

import typed_dotenv

secrets = typed_dotenv.load() # ".env" is the default value
assert type(secrets["DEBUG_IN_PRODUCTION"]) is bool

We used python-style values, but other syntaxes are available:

  • values: yaml 1.2 to use YAML 1.2 literalspip install typed_dotenv[yaml]
  • values: yaml 1.1 to use YAML 1.1 literals (differences from YAML 1.2). For now, this has the same effect as values: yaml 1.2pip install typed_dotenv[yaml]
  • values: toml to use TOML literals: 12:35:24 resolves to a datetime.time, etc.pip install typed_dotenv[toml]
  • values: json to use JSON literals

Now, up until now, we've only seen how to get those variables into a dict.

The recommended way: using Pydantic's BaseModel with load_into

This way, you have IDE autcompletion and type checking, and pydantic will raise errors if the value is not of the right type:

from pydantic import BaseModel
import typed_dotenv

class Secrets(BaseModel):
    GITHUB_TOKEN: str
    DEBUG_IN_PRODUCTION: bool

secrets = typed_dotenv.load_into(Secrets, filename="my_dotenv.env")

Production usage without a dotenv file

You can also use load_into with None as the filename to load variables from the environment:

from pydantic import BaseModel
import typed_dotenv

class Secrets(BaseModel):
    GITHUB_TOKEN: str
    DEBUG_IN_PRODUCTION: bool

secrets = typed_dotenv.load_into(Secrets, filename=None)

In that case, the value syntax will always be YAML 1.2 (we need a format that allows raw strings, since env variables are unquoted).

Manually coerce from os.getenv calls

You might still want to load these values as environment variables, but need to get type coersion. This time, since the value is gotten via os.getenv, typed_dotenv does not know the file's contents. The syntax used is thus declared when coercing types:

from os import getenv
import typed_dotenv

print(os.getenv("MY_ENV_VARIABLE"))
print(
    typed_dotenv.coerce(
        os.getenv("MY_ENV_VARIABLE"),
        typed_dotenv.VALUE_FORMATS.python_literal
    )
)

You can also make yourself a function to avoid declaring the values' format each time:

def env_coerce(key: str) -> Any:
    return typed_dotenv.coerce(
        os.getenv(key),
        typed_dotenv.VALUE_FORMATS.python_literal
    )

And use it like so:

print(env_coerce("MY_ENV_VARIABLE"))