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.3.tar.gz (13.9 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.3-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: envly-0.1.3.tar.gz
  • Upload date:
  • Size: 13.9 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.3.tar.gz
Algorithm Hash digest
SHA256 f625c04ee34b32c686ef731b5f05b5b69d783b1240e56ae0837a37e9dfd40442
MD5 540ae2e34819738b97cfc80a098a47db
BLAKE2b-256 c39b1ccd6d2b18515ada7f9e58d4f53b0235d1a355eb49b7e4d30ee15676b5fa

See more details on using hashes here.

File details

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

File metadata

  • Download URL: envly-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 20.0 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b3899c26347154129458b5da19eb06aeb10770db9e0d166f90b2479043f085f3
MD5 5733c3234f2ab042238a77115abb304f
BLAKE2b-256 3e41ddd5228d4ca759434e6733a307929dfc84f2d7c2844c3f07ccbe3819108e

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