From 1e700a485d2a4d7ddf5694243d9619ac4c7a0d32 Mon Sep 17 00:00:00 2001 From: Dominik Krenn Date: Tue, 1 Jul 2025 10:19:59 +0200 Subject: [PATCH] docs: enhance README.md with detailed examples and features for environment loader fix: update version to 0.2.2 in setup.py feat: import json in env.py for future enhancements --- README.md | 98 ++++++++++++++++++++++++++++++++++++++++++++----- multinut/env.py | 1 + setup.py | 2 +- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1bc09d2..de5e763 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,104 @@ -# multinut +# `multinut` -The multitool nobody asked for. Includes stuff and so +The multitool nobody asked for. Includes stuff and so. --- ## `multinut.env` A simple but flexible environment loader. + Supports `.env` file parsing with optional mode suffixes (e.g. `.env.production`, `.env.testing`, etc.), lazy loading, and dynamic access. -Useful when: +### Features -* You want a single class that can load environment configs based on mode (`development`, `production`, etc.) -* You need access via `env["KEY"]`, `env.get("KEY")`, or even `env.KEY` -* You want optional type casting and sane default handling -* You *don’t* want `os.environ` to be touched +* Mode-based config support (`.env.production`, `.env.testing`, etc.) +* Access via: + + * `env["KEY"]` + * `env.get("KEY", ...)` + * `env.KEY` +* Optional type casting (`str`, `bool`, `list`, `dict`, etc.) +* Sane default handling +* Does **not** mutate `os.environ` + +--- ### Use cases * Loading `.env` files in mode-aware Python projects * Separating secrets and configs by deployment context -* Dynamically reading values like `env.DB_URL`, `env.get("DEBUG", default=False, cast=bool)` -* Avoiding external dependencies beyond `python-dotenv` +* Dynamically reading values like `env.DB_URL`, `env.get("DEBUG", default=False, cast=cast_bool)` +* Avoiding `os.environ` pollution + +--- + +### Basic Example + +```python +from multinut.env import Environment, Modes + +env = Environment(env_file_name=".env", mode=Modes.DEVELOPMENT) + +print(env.get("DEBUG", default=False)) +print(env["API_KEY"]) +print(env.DB_URL) +``` + +Given a `.env.development` file: + +```env +DEBUG=true +API_KEY=secret +DB_URL=https://example.com +``` + +--- + +### Smart Casts Example + +```python +from multinut.env import ( + Environment, cast_bool, cast_int, cast_float, + cast_list, cast_tuple, cast_dict, cast_none_or_str +) + +env = Environment() + +print("INT:", env.get("PORT", cast=cast_int)) # -> int +print("FLOAT:", env.get("PI", cast=cast_float)) # -> float +print("BOOL:", env.get("ENABLED", cast=cast_bool)) # -> bool +print("LIST:", env.get("NUMBERS", cast=cast_list)) # -> list[str] +print("TUPLE:", env.get("WORDS", cast=cast_tuple)) # -> tuple[str] +print("DICT:", env.get("CONFIG", cast=cast_dict)) # -> dict +print("NONE_OR_STR:", env.get("OPTIONAL", cast=cast_none_or_str)) # -> None or str +``` + +Example `.env`: + +```env +PORT=8080 +PI=3.1415 +ENABLED=yes +NUMBERS=1,2,3 +WORDS=hello,world,test +CONFIG={"timeout": 30, "debug": true} +OPTIONAL=null +``` + +### Included Cast Helpers + +All built-in cast functions handle common edge cases: + +| Cast Function | Description | +| ------------------ | ------------------------------------------- | +| `cast_str` | Ensures string | +| `cast_int` | Converts to integer | +| `cast_float` | Converts to float | +| `cast_bool` | Accepts `1`, `true`, `yes`, `on`, etc. | +| `cast_list` | Comma-split list | +| `cast_tuple` | Comma-split, converted to tuple | +| `cast_dict` | Parses JSON string into dictionary | +| `cast_none_or_str` | Returns `None` if value is `null` or `None` | + +--- diff --git a/multinut/env.py b/multinut/env.py index 80905d2..09fd878 100644 --- a/multinut/env.py +++ b/multinut/env.py @@ -2,6 +2,7 @@ import os.path as op from typing import Optional, Callable, Any from enum import Enum from dotenv import dotenv_values +import json NO_DEFAULT = object() # Used to indicate no default value was provided diff --git a/setup.py b/setup.py index 910b347..d12ee6a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='multinut', - version='0.2.1.post1', + version='0.2.2', packages=find_packages(), install_requires=["dotenv"], author='Chipperfluff',