Python

Typed variables in Python

python -m envless_env types writes a typed module from your Envless variable names, types and visibility, so mypy and pyright catch a misspelt name or a server variable read on the client.

python -m envless_env types reads the names, types and visibility of your variables, never their values, and writes envless_env_types.py: the same env objects, typed by name. A misspelt name, a server variable read through the client view, or a bool used as a str then fails your type check instead of your deploy.

bash
python -m envless_env types
python
from envless_env_types import client_env, env, optional

database_url: str = env.DATABASE_URL
port: float = env.PORT
public_url: str = client_env.PUBLIC_API_URL
sentry_dsn: str | None = optional.SENTRY_DSN

It is the Python counterpart of envless types, which writes envless-env.d.ts for TypeScript. The module costs nothing at runtime: each name is the matching view from envless_env.server or envless_env.client, cast to a generated Protocol. Commit it, it holds no secrets.

What it writes

NameViewTypes
envenvless_env.server.envEvery variable
optionalenvless_env.server.optionalEvery variable, each | None
client_envenvless_env.client.envClient variables only
client_optionalenvless_env.client.optionalClient variables only, each | None

A string variable is typed str, a boolean is bool, and a number is float. That last one is deliberate: a number reads back as an int when it is whole and a float when it is not, and float accepts both, so a fractional sample rate is typed as honestly as a port. Wrap it in int() where an API wants an integer, or validate with pydantic-settings.

Names that are Python keywords, class for instance, are listed in a comment at the top of the file and stay readable by subscription, env['class']. Names that start and end with two underscores are reserved by Python, listed in a second comment, and readable only from the dict load() returns.

Where the names come from

Under envless run, the command reads the manifest the CLI injected and makes no request, so your envless login session is all it needs:

bash
envless run -- python -m envless_env types

Outside it, the command fetches the metadata with the same machine credentials the loader uses: ENVLESS_TOKEN, with the project and environments from the flags, from ENVLESS_PROJECT and ENVLESS_ENV, or from .envless. No decryption key is needed. Under envless run the flags below are ignored, and the command says so, because the CLI has already chosen the environments: pass --env to envless run instead. Every environment in .envless is read and merged in order, so a name that only exists in staging is still typed, with the type of the first environment that declares it.

FlagPurpose
--output PATHThe file to write, envless_env_types.py by default
--project SLUGThe project, instead of ENVLESS_PROJECT or .envless
--product SLUGThe product, when the project slug exists in more than one
--env SLUGSComma separated environments, merged in order
--checkExit 1 when the file is missing or stale, and write nothing

Keeping it current

Run it again whenever you add, rename or retype a variable. In CI, --check fails the job when the committed file no longer matches Envless:

bash
envless run --env production -- python -m envless_env types --check

The file is formatted the way Black and Ruff format code by default, so running your formatter over it changes nothing and --check stays green. Leave it out of hand edits: the next run replaces it.