Skip to main content

tidyenv logo

tidyenv

PyPI Python CI License: MIT

Typed environment variables with friendly errors. Built-in .env support. Zero dependencies.

from tidyenv import env

PORT = env.int("PORT", default=8000)
DEBUG = env.bool("DEBUG", default=False)
HOSTS = env.list("ALLOWED_HOSTS", default=["localhost"])
DATABASE_URL = env.str("DATABASE_URL")

Each line converts the type, applies the default, and if something is wrong, says exactly what:

tidyenv.EnvError: 1 environment problem:
  - PORT: expected an integer (got 'eighty')

Why

Plain os.environ tidyenv
Number with a default int(os.environ.get("PORT", "8000")) env.int("PORT", default=8000)
Bad number ValueError: invalid literal for int() with base 10: 'eighty' (which variable?) PORT: expected an integer (got 'eighty')
Missing variable KeyError: 'DB_URL' DB_URL: is not set
Empty value DB_URL= silently "" treated as not set
Boolean os.environ.get("DEBUG", "").lower() in ("1", "true", "yes"), and a typo like ture silently means False env.bool("DEBUG", default=False), and ture is an error
List [h.strip() for h in os.environ.get("HOSTS", "").split(",") if h.strip()] env.list("HOSTS")
List of numbers the same, plus int() on every item env.list("PORTS", of=int)
One of several values if mode not in ("dev", "prod"): raise ... env.choice("MODE", ["dev", "prod"])
Secrets in errors the value ends up in your logs secret=True shows ***
.env file pip install python-dotenv + load_dotenv() the same load_dotenv(), built in
Types for mypy / IDE str | None, cast it yourself env.int returns int
Several broken variables crash, fix, redeploy, crash on the next one env.collect() lists them all at once

One line per variable, and every error names the variable and says what is wrong with it.

Install

pip install tidyenv

Python 3.9+.

Usage

Reader Example value Returns
env.str(name) hello str (stripped)
env.int(name) 8000, 1_000 int
env.float(name) 0.25 float (nan and inf are rejected)
env.bool(name) true/false, yes/no, on/off, 1/0, any case bool
env.list(name, sep=",", of=str) a, b, c list (use of=int to convert items; int, float and bool follow the rules above)
env.choice(name, choices) prod str that must be in choices
env.path(name, must_exist=False) ~/data pathlib.Path with ~ expanded
env.json(name) {"a": 1} parsed JSON

Every reader takes an optional default. Without one, a missing or empty variable is an error. With one, the default is returned instead, and type checkers know the result is int | <type of default>.

.env files

Coming from python-dotenv? Change the import, nothing else:

from tidyenv import load_dotenv  # was: from dotenv import load_dotenv

load_dotenv()

load_dotenv, dotenv_values and find_dotenv take the same arguments and give the same results as python-dotenv 1.2's:

  • the same file syntax: export, comments, quotes, values spanning several lines, ${VAR} and ${VAR:-default};
  • .env is looked up starting next to the calling file, then in its parents;
  • values go into os.environ, and variables that are already set are kept unless override=True, so the first file you load wins;
  • PYTHON_DOTENV_DISABLED=1 turns loading off.

The test suite checks this against python-dotenv itself on thousands of generated files. Not included: set_key, unset_key, get_key and the dotenv command.

Then read the values with types. env reads os.environ, so it sees them:

from tidyenv import env, load_dotenv

load_dotenv()
PORT = env.int("PORT", default=8000)

Without touching os.environ. env.read_dotenv() follows the same rules but keeps the values inside env, and a line it can't parse is an error naming the file and line (python-dotenv only logs a warning):

env.read_dotenv()  # ./.env in the current directory, if it exists
env.read_dotenv("config/dev.env", required=True)

Need just the parser? tidyenv.parse_dotenv(text) returns a dict (without ${VAR} expansion).

All errors at once (optional)

By default the first bad variable raises EnvError. If you'd rather see every problem in one go, wrap your reads in env.collect():

with env.collect():
    PORT = env.int("PORT", default=8000)
    DATABASE_URL = env.str("DATABASE_URL")
    API_KEY = env.str("API_KEY", secret=True)
tidyenv.EnvError: 3 environment problems:
  - PORT: expected an integer (got 'eighty')
  - DATABASE_URL: is not set
  - API_KEY: is not set

Failed reads return None inside the block, so only use the values after it exits.

More

Secrets. Pass secret=True and the raw value is shown as *** in error messages, including errors about single env.list items.

Prefixes and custom sources. Build your own reader:

from tidyenv import Env

env = Env(prefix="MYAPP_")  # reads MYAPP_PORT for env.int("PORT")
test_env = Env(environ={"PORT": "1"})  # any mapping, handy in tests

Handling errors. EnvError.problems is a list of Problem(name, message), so you can print them your own way.

License

MIT. The .env support is adapted from python-dotenv (BSD-3-Clause, see LICENSES/python-dotenv.txt); thanks to its authors. tidyenv is not affiliated with or endorsed by the python-dotenv project.

Metadata

Release files for tidyenv 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tidyenv 0.2.0
File Size Uploaded
tidyenv-0.2.0.tar.gz 82.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tidyenv 0.2.0
File Interpreter ABI Platform
tidyenv-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 98.2 kB

Release files / tidyenv-0.2.0.tar.gz

Download URL tidyenv-0.2.0.tar.gz
Size 82.6 kB
Tags Source
SHA-256 checksum
How to use checksums
40506ac739e8fc5ad4d287069c932af7135eccf37188587c78ac595176f12877
BLAKE2b-256 checksum
How to use checksums
760000daa502fb2b68893002dc8f341271b6e28b7838f6e6f3c6b72971e57f01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / tidyenv-0.2.0-py3-none-any.whl

Download URL tidyenv-0.2.0-py3-none-any.whl
Size 15.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d965a3a8330dfcd2e40df559c4c3f4c6fcac18e05081f91d29ae24964b38ec7
BLAKE2b-256 checksum
How to use checksums
a79a18762f4f95c26402cd086c5d4afaa736b543c4d96a838d6aa486e7984715
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page