Skip to main content

Minimal, type-safe environment variable validation for Python.

Project description

envly

Minimal, type-safe environment variable validation for Python.

from envly import Environment, StringVar, IntVar, BoolVar

class MyEnv(Environment, prefix="APP_"):
    HOST    = StringVar()
    PORT    = IntVar(default=8080)
    DEBUG   = BoolVar(default=False)
    TOKEN   = StringVar(secret=True)

env = MyEnv()
print(env.PORT)   # 8080 —> int, not str
print(env.TOKEN)  # SecretStr('**redacted**')

Installation

pip install envly

Declaring fields

Every field is declared using a factory function. The type is baked into the function, so no annotations are required!

Function Returns Notes
StringVar() str Supports secret=True
IntVar() int
FloatVar() float
BytesVar() bytes
BoolVar() bool Accepts true/false/1/0/yes/no/on/off
EnumVar(*choices) str Restricts to allowed values
UrlVar() str Requires scheme and host
EmailVar() str
PathVar() Path Returns pathlib.Path
RegexVar(pattern) str Must fully match
ListVar(of=) list[T] Split from a delimited string
JsonVar() Any Parsed with json.loads

Options

All var functions share these keyword arguments:

PORT = IntVar(
    default=8080,                        # used when the var is not set
    validate=lambda x: x < 65536,       # single validator
    validate=(                           # or a tuple of validators
        lambda x: x > 1024,
        lambda x: x < 65536,
    ),
    var_name="SERVICE_PORT",             # override the env var key
)

ListVar (sub-coercion per element)

PORTS   = ListVar(of=int)                   # "8080,9000" -> [8080, 9000]
TAGS    = ListVar(of=str, sep=";")          # "api;bot"   -> ["api", "bot"]
ALLOWED = ListVar(of=int, validate=lambda xs: all(x < 65536 for x in xs))

Whitespace around each element is stripped automatically. Errors include the offending index: [PORTS[1]] expected an integer, got 'nope'.

JsonVar (optional type assertion)

SETTINGS = JsonVar()           # any valid JSON
SETTINGS = JsonVar(type=dict)  # asserts the root value is a dict

StringVar(secret=True) (redacted values)

TOKEN = StringVar(secret=True)

Returns a SecretStr, which is a str subclass that redacts itself in __repr__ and __str__, so secrets don't leak into logs. The raw value is accessible via .reveal().

print(env.TOKEN)           # **redacted**
print(env.TOKEN.reveal())  # the-actual-secret

Prefix

Pass prefix on the class definition to prepend a namespace to every var name.

class MyEnv(Environment, prefix="APP_"):
    PORT = IntVar(default=8080)  # reads APP_PORT

var_name always takes precedence over the prefix, letting you opt individual fields out:

class MyEnv(Environment, prefix="APP_"):
    DB_URL = StringVar(var_name="DATABASE_URL")  # reads DATABASE_URL, not APP_DATABASE_URL

Schema

Environment.schema() returns a typed description of every declared field — useful for documentation, startup health checks, or generating .env templates.

for field in MyEnv.schema():
    req = "required" if field["required"] else f"default={field.get('default')!r}"
    print(f"{field['env_var']:<20} {field['type']:<12} {req}")
APP_HOST             str          required
APP_PORT             int          default=8080
APP_DEBUG            bool         default=False
APP_TOKEN            SecretStr    required

Inheritance

MyEnvs compose naturally through class inheritance.

class BaseMyEnv(Environment):
    LOG_LEVEL = EnumVar("debug", "info", "warn", "error", default="info")
    DEBUG     = BoolVar(default=False)

class MyEnv(BaseMyEnv, prefix="APP_"):
    HOST  = StringVar()
    PORT  = IntVar(default=8080)

Testing

Pass _source to substitute os.environ with a plain dict.

env = MyEnv(_source={"APP_HOST": "localhost", "APP_PORT": "9000"})

Immutability

MyEnv instances are frozen after construction. Any attempt to set an attribute raises AttributeError.

env.PORT = 1  # AttributeError: MyEnv instances are immutable after construction.

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

envly-0.1.1.tar.gz (3.8 kB view details)

Uploaded Source

Built Distribution

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

envly-0.1.1-py3-none-any.whl (3.8 kB view details)

Uploaded Python 3

File details

Details for the file envly-0.1.1.tar.gz.

File metadata

  • Download URL: envly-0.1.1.tar.gz
  • Upload date:
  • Size: 3.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for envly-0.1.1.tar.gz
Algorithm Hash digest
SHA256 340610e739a4d8a274b1fd19b80c6a348740bd9de1aa105ebd9d26b8f950d8e3
MD5 69eac4d6b45d4be06d75a515eb6776fd
BLAKE2b-256 d53f6d065e4d63cbb8da9a1877034fd47157bd87f446bf9f4ebd7995c9d50cab

See more details on using hashes here.

File details

Details for the file envly-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: envly-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 3.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for envly-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3bc8da9f1956f4ab18c6e1a9d4041ff16e7a8cd5ccb28b57fe843e2641879d57
MD5 86e8bd3b619434407ca2c1eb22b43911
BLAKE2b-256 dcc40bd8f27bd2e7117b2e7b0ac57f673ae3f39e66af973bd09920bb7d2f4f53

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