Skip to main content

Strictly typed environment variable parsing and validation with msgspec.

Project description

strictenv

strictenv is a fast, strictly typed environment variable loader built on top of msgspec. It gives you explicit schemas, predictable coercion, and runtime validation with a small API.

Install

uv add strictenv

Quickstart

from __future__ import annotations

from typing import Annotated

from msgspec import Struct

from strictenv import BaseSettings, Field, TransformStruct, transform


class Database(TransformStruct):
    host: str
    port: int

    @transform("host", mode="before")
    def normalize_host(value: str) -> str:
        return value.strip().lower()


class AppSettings(BaseSettings):
    debug: bool
    database: Database
    tenant_id: Annotated[str, Field(alias="TENANT")]

    model_config = {
        "env_prefix": "APP_",
        "case_sensitive": False,
        "env_nested_delimiter": "__",
        "env_file": ".env",
        "strict_env_file": True,
    }


settings = AppSettings.load()
AppSettings.write_env_example(".env.example")

Examples:

  • APP_DEBUG=true -> debug: bool
  • APP_DATABASE={"host":"localhost","port":5432} -> database: Database
  • APP_DATABASE__HOST=localhost + APP_DATABASE__PORT=5432 -> nested parsing
  • APP_TENANT=acme -> tenant_id via alias

model_config

Key Type Default Description
env_prefix str "" Prefix applied to all environment keys.
case_sensitive bool False When False, key lookup is case-insensitive.
env_nested_delimiter str | None None Enables nested mapping like DB__HOST.
env_file str | None None Path to a .env file to load first.
strict_env_file bool True When True, invalid/missing .env files raise explicit errors.
max_nested_struct_depth int | None None Maximum allowed depth for nested Struct traversal.

Field(...)

Field works both in Annotated[...] and as a default value:

from typing import Annotated
from strictenv import BaseSettings, Field

class AppSettings(BaseSettings):
    # Annotated metadata style
    retries: Annotated[int, Field(gt=0, lt=10)]

    # Default value style (alias + default + description)
    tenant_id: str = Field("acme", alias="TENANT", description="Tenant identifier")

    # Required when using `...`
    token: str = Field(...)

Supported quick validations:

  • gt, ge, lt, le
  • min_length, max_length

Description source priority for metadata/examples:

  • Field(description=...) (highest priority)
  • attribute docstring right below the field

@transform(...) And TransformStruct

Use @transform(field_name, mode="before" | "after") on classes that inherit from TransformStruct (including BaseSettings).

  • before receives raw string input and may return:
    • another str (then normal coercion runs), or
    • a value already in target type.
  • after receives already parsed value and must keep a compatible runtime type.
from strictenv import BaseSettings, TransformStruct, transform

class DatabaseConfig(TransformStruct):
    host: str
    port: int

    @transform("host", mode="before")
    def normalize_host(value: str) -> str:
        return value.strip().lower()

    @transform("port", mode="after")
    def keep_int(value: int) -> int:
        return value + 1

class AppSettings(BaseSettings):
    database: DatabaseConfig

Rules:

  • field_name must be top-level in that class (no dotted paths).
  • Multiple transforms run in definition order.
  • Nested transforms apply only when nested type inherits TransformStruct.
  • Nested settings can still use plain msgspec.Struct; use TransformStruct only when you need @transform.

Generate .env.example

BaseSettings.write_env_example(path) writes an empty env template for the schema. Field descriptions are emitted as comments:

class AppSettings(BaseSettings):
    debug: bool = Field(..., description="Enable debug logs")
    tenant_id: str = Field(..., alias="TENANT", description="Tenant identifier")

AppSettings.write_env_example(".env.example")

Generated file:

# Enable debug logs
DEBUG=

# Tenant identifier
TENANT=

Value precedence

  1. overrides argument in load(...)
  2. env argument (or os.environ when env=None)
  3. .env file configured with model_config["env_file"]
  4. Field defaults in the settings struct

If no source provides a required field, MissingSettingError is raised. If env_file is configured but missing, EnvFileNotFoundError is raised. If env_file cannot be read, EnvFileReadError is raised. If a non-comment line in env_file is not valid KEY=VALUE, EnvFileFormatError is raised. If keys collide in case-insensitive mode, EnvKeyConflictError is raised. If nested struct depth exceeds max_nested_struct_depth, NestedStructDepthError is raised. With strict_env_file=False, .env file errors are tolerated and invalid lines are skipped.

Coercion rules

strictenv performs strict coercion for:

  • bool, int, float, str
  • Enum (by member name or value)
  • datetime, date, time
  • timedelta (ISO8601, HH:MM[:SS], or numeric seconds)
  • msgspec.Struct (from JSON string)
  • list, dict, tuple, set, Mapping (from JSON string)
  • Union / Optional (tries non-None members in order)

Invalid values raise ParseSettingError. There is no silent fallback to raw strings. Transform registration/execution failures raise TransformSettingError.

.env parser features:

  • Optional export prefix (export KEY=value)
  • Inline comments for unquoted values (KEY=value # comment)
  • Quoted values with escapes and multiline support
  • Variable expansion via ${VAR} (including references to earlier/later keys)

Differences vs pydantic-settings

  • API is intentionally smaller and focused on msgspec.Struct.
  • Compatibility is partial (supports familiar model_config, aliases, and nested env parsing).
  • Automatic field description injection into msgspec.Meta is supported.

Development

uv sync --dev
uv run ruff check .
uv run mypy src
uv run pytest
uv build

Publish

uv publish --dry-run
uv publish

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

strictenv-0.1.0.tar.gz (19.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

strictenv-0.1.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file strictenv-0.1.0.tar.gz.

File metadata

  • Download URL: strictenv-0.1.0.tar.gz
  • Upload date:
  • Size: 19.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for strictenv-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d24b1c8aa84cacaa9dfff12f3d14a0389012e2be61ff1f29bc29d949eb176a56
MD5 14ee6b9d2e174a988631de26be734a95
BLAKE2b-256 59daa3569ed21f055b5d3f34b299265e80d26d556c95d184050f387bb57348a1

See more details on using hashes here.

File details

Details for the file strictenv-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: strictenv-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for strictenv-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 40ec8a847783b668a08514b25a5cb5be2a6bee1504ec5a3a9928cd01a3eb3d49
MD5 be4c6d7f46470bcd7fcdb81cf39a0884
BLAKE2b-256 84fd10f1fb545159e5b567690f83ce3cfed1295182f85d908e9b292d7de57ce6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page