A simple .env file parser with types for Python
Project description
๐ Envist
The most powerful environment variable manager for Python ๐
Environment variables have never been this smart!
Transform your .env
files from simple key-value pairs into type-safe, intelligent configuration management with automatic typecasting, variable expansion, and zero-hassle setup.
Created with โค๏ธ by Md. Almas Ali
๐ Table of Contents
- ๐ฏ Why Envist?
- โจ Key Features
- โก Quick Start
- ๐ฆ Installation
- ๐ Complete Guide
- ๐ API Reference
- ๐ท๏ธ Supported Data Types
- ๐ง Configuration
- ๐ ๏ธ Exception Handling
- ๐ก๏ธ Environment Variable Validation
- ๐ Logging
- ๐ฏ Examples
- ๐ค Contributing
- ๐ License
๐ฏ Why Envist?
Stop wrestling with environment variables!
Traditional .env
files are just strings. What if your environment variables could be intelligent? What if they could know their own types, expand references to other variables, and validate themselves?
Envist makes this possible:
# Traditional .env (boring ๐ด)
PORT=8080
DEBUG=True
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# Envist .env (intelligent ๐ง )
PORT <int> = 8080
DEBUG <bool> = True
HOST <str> = localhost
DATABASE_URL <str> = postgresql://user:pass@${HOST}:5432/mydb
ALLOWED_HOSTS <list> = localhost, 127.0.0.1, 0.0.0.0
CONFIG <json> = {"timeout": 30, "retries": 3}
The result? Type-safe, self-documenting configuration that prevents bugs before they happen!
โจ Key Features
๐ฏ In-File Type Casting - Define variable types directly in your .env
file
๐ Variable Expansion - Reference other variables with ${variable}
syntax
๐ก๏ธ Type Safety - Automatic validation and conversion to Python types
๐ Rich Data Types - Support for lists, dicts, JSON, CSV, and more
๐ Backward Compatible - Works with existing .env
files
โก Zero Dependencies - Lightweight and fast
๐ง Full CRUD Operations - Get, set, unset, and save variables
โ
Environment Validation - @validator
decorator for custom validation rules
๐ Built-in Logging - Comprehensive debugging capabilities
โก Quick Start
1. Install Envist
pip install envist
2. Create your smart .env
file
# .env
APP_NAME <str> = My Awesome App
PORT <int> = 8080
DEBUG <bool> = True
ALLOWED_HOSTS <list> = localhost, 127.0.0.1
DATABASE_CONFIG <json> = {"host": "localhost", "port": 5432}
BASE_URL <str> = http://localhost:${PORT}
3. Use it in your Python code
from envist import Envist
env = Envist()
# โจ Automatic type casting!
app_name = env.get('APP_NAME') # Returns: 'My Awesome App' (str)
port = env.get('PORT') # Returns: 8080 (int)
debug = env.get('DEBUG') # Returns: True (bool)
hosts = env.get('ALLOWED_HOSTS') # Returns: ['localhost', '127.0.0.1'] (list)
db_config = env.get('DATABASE_CONFIG') # Returns: {'host': 'localhost', 'port': 5432} (dict)
base_url = env.get('BASE_URL') # Returns: 'http://localhost:8080' (str)
print(f"๐ Starting {app_name} on port {port}")
print(f"๐ Debug mode: {debug}")
print(f"๐ Base URL: {base_url}")
That's it! No more manual type conversion, no more configuration headaches! ๐
๐ฆ Installation
Using pip (Recommended)
pip install envist
Using conda
conda install -c conda-forge envist
Using poetry
poetry add envist
From source
git clone https://github.com/Almas-Ali/envist.git
cd envist
pip install -e .
Requirements: Python 3.7+ (No external dependencies!)
๐ Complete Guide
Basic Usage
Creating an Envist Instance
from envist import Envist
# Use default .env file with auto-casting enabled
env = Envist()
# Use custom file path
env = Envist(path="config/.env")
# Disable auto-casting (values remain as strings unless explicitly cast)
env = Envist(auto_cast=False)
# Accept empty values (variables declared without values)
env = Envist(accept_empty=True)
# Full configuration
env = Envist(
path="config/production.env",
accept_empty=True,
auto_cast=True
)
Getting Variables
# Get with automatic type casting (from .env type annotations)
value = env.get('VARIABLE_NAME')
# Get with default value
value = env.get('VARIABLE_NAME', default='fallback_value')
# Get with explicit type casting (overrides .env annotations)
value = env.get('VARIABLE_NAME', cast=int)
# Get all variables
all_vars = env.get_all()
Type Casting Magic
๐ฏ The game-changer: Define types directly in your .env
file!
Traditional .env (Before Envist)
DATABASE_PORT=5432
DEBUG=true
ALLOWED_IPS=192.168.1.1,192.168.1.2,localhost
import os
# ๐ Everything is a string, manual conversion needed
port = int(os.getenv('DATABASE_PORT')) # Manual casting
debug = os.getenv('DEBUG').lower() == 'true' # Manual parsing
ips = os.getenv('ALLOWED_IPS').split(',') # Manual splitting
Smart .env (With Envist)
DATABASE_PORT <int> = 5432
DEBUG <bool> = true
ALLOWED_IPS <list> = 192.168.1.1, 192.168.1.2, localhost
from envist import Envist
env = Envist()
# โจ Automatic type casting!
port = env.get('DATABASE_PORT') # Already an int!
debug = env.get('DEBUG') # Already a bool!
ips = env.get('ALLOWED_IPS') # Already a list!
Supported Type Annotations
Syntax | Python Type | Example |
---|---|---|
<str> |
str |
NAME <str> = John Doe |
<int> |
int |
PORT <int> = 8080 |
<float> |
float |
PI <float> = 3.14159 |
<bool> |
bool |
DEBUG <bool> = True |
<list> |
list |
HOSTS <list> = web1, web2, web3 |
<tuple> |
tuple |
COORDS <tuple> = 10, 20, 30 |
<set> |
set |
TAGS <set> = python, web, api |
<dict> |
dict |
CONFIG <dict> = key1:value1, key2:value2 |
<json> |
dict |
SETTINGS <json> = {"timeout": 30} |
<csv> |
list |
DATA <csv> = name,age,email |
Variable Expansion
๐ Reference other variables like a pro!
# .env - Smart variable references
HOST <str> = localhost
PORT <int> = 8080
PROTOCOL <str> = https
# Variable expansion in action
API_BASE <str> = ${PROTOCOL}://${HOST}:${PORT}/api
DATABASE_URL <str> = postgresql://user:pass@${HOST}:5432/mydb
REDIS_URL <str> = redis://${HOST}:6379
# Nested expansion
APP_URL <str> = ${API_BASE}/v1
HEALTH_CHECK <str> = ${APP_URL}/health
from envist import Envist
env = Envist()
print(env.get('API_BASE')) # https://localhost:8080/api
print(env.get('DATABASE_URL')) # postgresql://user:pass@localhost:5432/mydb
print(env.get('HEALTH_CHECK')) # https://localhost:8080/api/v1/health
Advanced Operations
Setting Variables
# Set single variable
env.set('NEW_VARIABLE', 'some_value')
# Set multiple variables
env.set_all({
'DATABASE_HOST': 'localhost',
'DATABASE_PORT': 5432,
'DATABASE_NAME': 'myapp'
})
# Variables are immediately available
db_host = env.get('DATABASE_HOST') # 'localhost'
Removing Variables
# Remove single variable
env.unset('TEMPORARY_VAR')
# Remove multiple variables
env.unset_all(['VAR1', 'VAR2', 'VAR3'])
# Remove all variables (be careful!)
env.unset_all()
Saving Changes
# Save to the same file
env.save()
# Save with formatting options
env.save(
pretty=True, # Add spacing for readability
sort_keys=True, # Sort variables alphabetically
example_file=False # Save an .env.example file with type annotations
)
Example of saved output:
# Automatically formatted and sorted
DATABASE_HOST <str> = localhost
DATABASE_PORT <int> = 5432
DEBUG <bool> = True
๐ API Reference
Class: Envist
Constructor
Envist(path: str = ".env", accept_empty: bool = False, auto_cast: bool = True)
Parameters:
path
(str, optional): Path to the environment file. Defaults to ".env".accept_empty
(bool, optional): Accept keys declared without values. Defaults to False.auto_cast
(bool, optional): Automatically cast values when type annotations are present. If False, values remain as strings. Defaults to True.
Core Methods
Method | Description | Parameters | Returns |
---|---|---|---|
get(key, *, default=None, cast=None) |
Get a variable with optional type casting | key : Variable namedefault : Fallback valuecast : Type to cast to (overrides auto_cast) |
Any |
get_all() |
Get all environment variables | None | dict |
set(key, value, cast=None) |
Set a single variable | key : Variable namevalue : Variable valuecast : Optional type casting hint |
Any |
set_all(data) |
Set multiple variables | data : Dictionary of key-value pairs |
None |
unset(key) |
Remove a single variable | key : Variable name |
None |
unset_all(data_list=None) |
Remove multiple or all variables | data_list : List of keys (if None, removes all) |
None |
save(pretty=False, sort_keys=False, example_file=False) |
Save changes to file | pretty : Format with spacessort_keys : Sort alphabeticallyexample_file : Create .env.example file |
None |
reload() |
Reload environment variables from file | None | None |
Dictionary-Style Access
Envist supports dictionary-style operations:
env = Envist()
# Dictionary-style access
value = env['DATABASE_URL'] # Same as env.get('DATABASE_URL')
env['NEW_VAR'] = 'value' # Same as env.set('NEW_VAR', 'value')
del env['OLD_VAR'] # Same as env.unset('OLD_VAR')
# Check if variable exists
if 'API_KEY' in env:
print("API_KEY is configured")
# Get number of variables
print(f"Total variables: {len(env)}")
# Iterate over keys
for key in env:
print(f"{key}: {env[key]}")
Properties
Property | Description | Type |
---|---|---|
path |
Path to the environment file | str |
Special Methods
Method | Description | Example |
---|---|---|
__getitem__() |
Dictionary-style access | env['KEY'] |
__setitem__() |
Dictionary-style assignment | env['KEY'] = 'value' |
__delitem__() |
Dictionary-style deletion | del env['KEY'] |
__contains__() |
Check if key exists | 'KEY' in env |
__len__() |
Get number of variables | len(env) |
__iter__() |
Iterate over keys | for key in env: |
__repr__() |
String representation | <Envist path=".env"> |
Decorator: @validator
The @validator
decorator provides environment variable validation at startup time.
@validator(env: Envist, key: str) -> Callable[[Callable[[Any], bool]], bool]
Parameters:
env
(Envist): The Envist instance to validate againstkey
(str): The environment variable key to validate
Usage:
@validator(env, "VARIABLE_NAME")
def validate_variable(value: Any) -> bool:
"""Validation logic here."""
if not meets_criteria(value):
raise ValueError("Validation failed")
return True
Note: The decorator uses an IIFE (Immediately Invoked Function Expression) pattern, so validation runs immediately when the decorator is applied.
๐ท๏ธ Supported Data Types
Envist supports a rich set of data types with intelligent parsing, including nested type support:
Simple Types
Type | Syntax | Description | Example | Python Output |
---|---|---|---|---|
String | <str> |
Text values | NAME <str> = John Doe |
'John Doe' |
Integer | <int> |
Whole numbers | PORT <int> = 8080 |
8080 |
Float | <float> |
Decimal numbers | PI <float> = 3.14159 |
3.14159 |
Boolean | <bool> |
True/False values | DEBUG <bool> = True |
True |
List | <list> or <array> |
Comma-separated values | HOSTS <list> = web1, web2, web3 |
['web1', 'web2', 'web3'] |
Tuple | <tuple> |
Immutable sequences | COORDS <tuple> = 10, 20, 30 |
(10, 20, 30) |
Set | <set> |
Unique values only | TAGS <set> = python, web, python |
{'python', 'web'} |
Dictionary | <dict> |
Key-value pairs | CONFIG <dict> = key1:value1, key2:value2 |
{'key1': 'value1', 'key2': 'value2'} |
JSON | <json> |
JSON objects | SETTINGS <json> = {"timeout": 30, "retries": 3} |
{'timeout': 30, 'retries': 3} |
CSV | <csv> or <comma_separated> |
CSV-style data | DATA <csv> = name,age,email |
['name', 'age', 'email'] |
๐ New: Nested Type Support
Envist now supports nested type annotations for complex data structures:
Typed Collections
# List of integers
PORTS <list<int>> = 8080, 9000, 3000
# Set of floats
PRICES <set<float>> = 19.99, 29.95, 19.99
# Tuple of strings
SERVERS <tuple<str>> = web1.example.com, web2.example.com
# List of boolean values
FEATURES <list<bool>> = true, false, true
from envist import Envist
env = Envist()
ports = env.get('PORTS') # [8080, 9000, 3000] (list of ints)
prices = env.get('PRICES') # {19.99, 29.95} (set of floats)
servers = env.get('SERVERS') # ('web1.example.com', 'web2.example.com') (tuple)
features = env.get('FEATURES') # [True, False, True] (list of bools)
Typed Dictionaries
# Dictionary with string keys and integer values
PORT_MAP <dict<str, int>> = web:8080, api:9000, db:5432
# Dictionary with string keys and float values
WEIGHTS <dict<str, float>> = cpu:0.7, memory:0.2, disk:0.1
# Dictionary with string keys and boolean values
FEATURES <dict<str, bool>> = auth:true, cache:false, debug:true
port_map = env.get('PORT_MAP') # {'web': 8080, 'api': 9000, 'db': 5432}
weights = env.get('WEIGHTS') # {'cpu': 0.7, 'memory': 0.2, 'disk': 0.1}
features = env.get('FEATURES') # {'auth': True, 'cache': False, 'debug': True}
Complex Nested Types
# Dictionary with string keys and list of integers as values
GROUPS <dict<str, list<int>>> = admins:1,2,3, users:4,5,6, guests:7,8,9
# List of dictionaries (using JSON syntax for complex structures)
ENDPOINTS <json> = [
{"name": "api", "port": 8080, "ssl": true},
{"name": "web", "port": 3000, "ssl": false}
]
๐ฏ Smart Type Detection
Boolean values are parsed intelligently:
# All of these become Python True
ENABLED <bool> = True
ENABLED <bool> = true
ENABLED <bool> = TRUE
ENABLED <bool> = 1
ENABLED <bool> = yes
ENABLED <bool> = on
# All of these become Python False
DISABLED <bool> = False
DISABLED <bool> = false
DISABLED <bool> = FALSE
DISABLED <bool> = 0
DISABLED <bool> = no
DISABLED <bool> = off
Lists and tuples handle whitespace gracefully:
# These are equivalent
HOSTS <list> = web1, web2, web3
HOSTS <list> = web1,web2,web3
HOSTS <list> = web1 , web2 , web3
JSON values support complex structures:
# Simple object
CONFIG <json> = {"debug": true, "timeout": 30}
# Nested structures
DATABASE <json> = {
"host": "localhost",
"credentials": {"user": "admin", "pass": "secret"},
"pools": [10, 20, 30]
}
๐ Backward Compatibility
No type annotations? No problem! Envist works with traditional .env
files:
# Traditional .env file (no type annotations)
DATABASE_URL=postgresql://localhost:5432/mydb
DEBUG=true
PORT=8080
from envist import Envist
env = Envist()
# Manual casting still works
url = env.get('DATABASE_URL', cast=str)
debug = env.get('DEBUG', cast=bool)
port = env.get('PORT', cast=int)
# Or with nested types
items = env.get('ITEMS', cast='list<int>')
โ๏ธ Advanced Type Casting Options
You can control type casting behavior:
# Auto-cast enabled (default) - types are cast automatically from annotations
env = Envist(auto_cast=True)
# Auto-cast disabled - all values remain as strings unless explicitly cast
env = Envist(auto_cast=False)
value = env.get('PORT', cast=int) # Manual casting required
# Accept empty values
env = Envist(accept_empty=True)
empty_val = env.get('OPTIONAL_SETTING') # Returns None if empty
๐ก Pro Tip: Mix and match! You can gradually migrate your
.env
files by adding type annotations to new variables while keeping existing ones unchanged.
๐ง Configuration
Custom File Paths
# Development environment
dev_env = Envist(path="environments/development.env")
# Production environment
prod_env = Envist(path="environments/production.env")
# Testing environment
test_env = Envist(path="tests/fixtures/test.env")
Environment-Specific Loading
import os
from envist import Envist
# Load environment-specific configuration
environment = os.getenv('ENVIRONMENT', 'development')
env_file = f"config/{environment}.env"
env = Envist(path=env_file)
print(f"๐ Loaded {environment} configuration from {env_file}")
Multiple Environment Files
from envist import Envist
# Base configuration
base_env = Envist(path="config/base.env")
# Environment-specific overrides
local_env = Envist(path="config/local.env")
# Merge configurations (local overrides base)
config = {**base_env.get_all(), **local_env.get_all()}
Exception Handling
Envist provides specific exception types for different error scenarios:
from envist import (
Envist,
EnvistError, # Base exception
EnvistCastError, # Type casting failures
EnvistParseError, # File parsing errors
EnvistValueError, # Value not found errors
EnvistTypeError, # Type validation errors
FileNotFoundError # File not found errors
)
try:
env = Envist(path="nonexistent.env")
except FileNotFoundError as e:
print(f"Environment file not found: {e}")
try:
value = env.get('PORT', cast='invalid_type')
except EnvistCastError as e:
print(f"Type casting failed: {e}")
try:
env.unset('NONEXISTENT_VAR')
except EnvistValueError as e:
print(f"Variable not found: {e}")
๐ Environment Variable Validation
Envist provides a powerful @validator
decorator that allows you to define custom validation rules for your environment variables. This ensures your configuration values meet specific business requirements before your application starts.
The @validator
Decorator
The @validator
decorator executes validation functions immediately when they are defined (using an IIFE - Immediately Invoked Function Expression pattern), ensuring your environment variables are validated at startup time.
Basic Usage
from envist import Envist, validator
env = Envist()
@validator(env, "PORT")
def validate_port(value: int) -> bool:
"""Validate that PORT is within valid range."""
if not (1 <= value <= 65535):
raise ValueError("PORT must be between 1 and 65535")
return True
@validator(env, "DATABASE_URL")
def validate_database_url(value: str) -> bool:
"""Validate DATABASE_URL format."""
if not value.startswith(('postgresql://', 'mysql://', 'sqlite://')):
raise ValueError("DATABASE_URL must start with a valid database scheme")
return True
@validator(env, "LOG_LEVEL")
def validate_log_level(value: str) -> bool:
"""Validate LOG_LEVEL is a valid logging level."""
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
if value.upper() not in valid_levels:
raise ValueError(f"LOG_LEVEL must be one of: {', '.join(valid_levels)}")
return True
Real-World Validation Examples
API Configuration Validation:
from envist import Envist, validator
import re
env = Envist()
@validator(env, "API_KEY")
def validate_api_key(value: str) -> bool:
"""Validate API key format and length."""
if len(value) < 32:
raise ValueError("API_KEY must be at least 32 characters long")
if not re.match(r'^[a-zA-Z0-9_-]+$', value):
raise ValueError("API_KEY contains invalid characters")
return True
@validator(env, "RATE_LIMIT")
def validate_rate_limit(value: int) -> bool:
"""Validate rate limit is reasonable."""
if not (1 <= value <= 10000):
raise ValueError("RATE_LIMIT must be between 1 and 10000 requests per hour")
return True
@validator(env, "ALLOWED_ORIGINS")
def validate_allowed_origins(value: list) -> bool:
"""Validate CORS origins are valid URLs."""
import urllib.parse
for origin in value:
try:
result = urllib.parse.urlparse(origin)
if not result.scheme or not result.netloc:
raise ValueError(f"Invalid origin URL: {origin}")
except Exception:
raise ValueError(f"Malformed origin URL: {origin}")
return True
Validator Best Practices
Practice | Description | Example |
---|---|---|
Early Validation | Run validators at application startup | Place validators after Envist() initialization |
Descriptive Messages | Provide clear error messages | "PORT must be between 1 and 65535" vs "Invalid port" |
Type Hints | Use proper type hints for clarity | def validate_port(value: int) -> bool: |
Comprehensive Checks | Cover edge cases and security requirements | Check format, range, security constraints |
Environment Awareness | Validate differently per environment | Stricter validation in production |
Cross-Validation | Validate relationships between variables | Ensure min โค max values |
๐ก Pro Tip: Use validators to catch configuration errors early and provide helpful error messages to developers setting up your application.
๐ Logging
Envist provides comprehensive logging capabilities with flexible configuration options to help you debug configuration issues and monitor environment variable usage.
Basic Logging Setup
import logging
from envist import Envist
# Enable basic logging to see Envist operations
logging.basicConfig(level=logging.INFO)
env = Envist()
# Now you'll see info-level logs about file loading and variable parsing
Advanced Logging Configuration
Envist uses a sophisticated logging system with multiple handler types and customizable configurations:
from envist.logger import EnvistLogger, configure_logger, create_stream_handler, create_file_handler
# Configure with custom handlers
logger = configure_logger(
handlers=[
create_stream_handler(level=logging.DEBUG), # Console output
create_file_handler("envist.log", level=logging.INFO) # File output
],
level="DEBUG"
)
# Now create Envist instance - it will use the configured logger
env = Envist()
Log Levels and Information
Level | Information Logged | Use Case |
---|---|---|
DEBUG |
Detailed parsing, type casting, variable resolution | Development & troubleshooting |
INFO |
File loading, variable counts, set/unset operations | General monitoring |
WARNING |
Missing variables, type casting issues | Production monitoring |
ERROR |
File read errors, parsing failures | Error tracking |
CRITICAL |
System-level failures | Critical error alerts |
Built-in Logging Methods
The Envist logger provides specialized logging methods:
from envist.logger import logger
# Environment parsing
logger.log_env_parse("/path/to/.env", 15) # File path and variable count
# Type casting operations
logger.log_typecast("PORT", "8080", "int", True) # Key, value, type, success
# Variable expansion
logger.log_variable_expansion("${HOST}:${PORT}", "localhost:8080")
# Variable access
logger.log_variable_access("DATABASE_URL", True, "str") # Key, found, cast_type
Handler Types
Envist supports multiple logging handler types for different use cases:
1. Console/Stream Handler
from envist.logger import create_stream_handler
import sys
# Console output (default to stdout)
console_handler = create_stream_handler(level=logging.INFO)
# Error output to stderr
error_handler = create_stream_handler(stream=sys.stderr, level=logging.ERROR)
2. File Handler
from envist.logger import create_file_handler
# Basic file logging
file_handler = create_file_handler("app.log", level=logging.DEBUG)
# Automatic directory creation
file_handler = create_file_handler("logs/envist/app.log", level=logging.INFO)
3. JSON Handler (Structured Logging)
from envist.logger import create_json_handler
# JSON formatted logs for log aggregation systems
json_handler = create_json_handler("app.json", level=logging.INFO)
JSON output format:
{
"timestamp": "2025-01-15T10:30:45.123456",
"level": "INFO",
"logger": "envist",
"module": "parser",
"function": "_load_env",
"line": 127,
"message": "Parsed .env: found 12 variables"
}
4. Rotating File Handler
from envist.logger import create_rotating_handler
# Rotate when file reaches 10MB, keep 5 backups
rotating_handler = create_rotating_handler(
"app.log",
max_bytes=10 * 1024 * 1024, # 10MB
backup_count=5,
level=logging.INFO
)
5. Syslog Handler
from envist.logger import create_syslog_handler
# Send logs to system logger
syslog_handler = create_syslog_handler(
address=("localhost", 514),
level=logging.WARNING
)
Example Log Output
Console Output (INFO level):
INFO: Initializing Envist with file: .env
INFO: Parsed .env: found 12 variables
INFO: Set environment variable 'NEW_VAR' = 'test_value'
WARNING: Variable 'OPTIONAL_VAR' not found
File Output (DEBUG level):
2025-01-15 10:30:45,123 - envist - DEBUG - _load_env:127 - Loading environment file: .env
2025-01-15 10:30:45,124 - envist - DEBUG - _apply_type_casting:234 - Successfully cast 'PORT' to int
2025-01-15 10:30:45,125 - envist - DEBUG - _resolve_variable:156 - Expanded '${HOST}:${PORT}' to 'localhost:8080'
2025-01-15 10:30:45,126 - envist - INFO - _load_env:145 - Parsed .env: found 12 variables
Production Logging Setup
For production environments, use a comprehensive logging configuration:
import logging
from pathlib import Path
from envist.logger import (
EnvistLogger,
create_stream_handler,
create_rotating_handler,
create_json_handler
)
# Production logging setup
def setup_production_logging():
log_dir = Path("/var/log/myapp")
log_dir.mkdir(exist_ok=True)
handlers = [
# Console: only warnings and errors
create_stream_handler(level=logging.WARNING),
# Rotating file: all info and above
create_rotating_handler(
log_dir / "app.log",
max_bytes=50 * 1024 * 1024, # 50MB
backup_count=10,
level=logging.INFO
),
# JSON for log aggregation (ELK, Splunk, etc.)
create_json_handler(
log_dir / "app.json",
level=logging.INFO
)
]
return EnvistLogger.configure(custom_handlers=handlers, reset=True)
# Use in production
logger = setup_production_logging()
logger.set_level("INFO")
# Your app code
env = Envist()
Development Logging Setup
For development, you might want more verbose logging:
from envist.logger import configure_logger, create_stream_handler, create_file_handler
# Development: verbose console + debug file
dev_logger = configure_logger(
handlers=[
create_stream_handler(level=logging.DEBUG), # All to console
create_file_handler("debug.log", level=logging.DEBUG) # All to file
],
level="DEBUG"
)
env = Envist()
Custom Logger Integration
You can integrate Envist logging with your existing application logger:
import logging
from envist.logger import EnvistLogger
# Get your existing application logger
app_logger = logging.getLogger("myapp")
# Create a custom handler that forwards to your app logger
class AppLoggerHandler(logging.Handler):
def __init__(self, app_logger):
super().__init__()
self.app_logger = app_logger
def emit(self, record):
# Forward Envist logs to your app logger with prefix
msg = f"[ENVIST] {self.format(record)}"
self.app_logger.log(record.levelno, msg)
# Configure Envist to use your app logger
custom_handler = AppLoggerHandler(app_logger)
envist_logger = EnvistLogger.configure(custom_handlers=[custom_handler])
env = Envist()
Dynamic Logging Control
You can change logging levels and handlers at runtime:
from envist.logger import logger, create_file_handler
# Change log level
logger.set_level("DEBUG")
# Add a new handler
debug_handler = create_file_handler("debug.log", level=logging.DEBUG)
logger.add_handler(debug_handler)
# Remove a handler
logger.remove_handler(debug_handler)
# Replace all handlers
new_handlers = [create_stream_handler(level=logging.INFO)]
logger.replace_handlers(new_handlers)
๐ Debugging Tips:
- Use
DEBUG
level when troubleshooting type casting issues- Use
INFO
level for monitoring environment loading in production- Use JSON handlers for centralized logging systems
- Use rotating handlers to prevent log files from growing too large
๐ฏ Examples
๐ Web Application Configuration
# config/web.env
APP_NAME <str> = My Web App
APP_VERSION <str> = 1.0.0
HOST <str> = localhost
PORT <int> = 8080
DEBUG <bool> = True
# Database
DB_HOST <str> = localhost
DB_PORT <int> = 5432
DB_NAME <str> = webapp
DB_URL <str> = postgresql://user:pass@${DB_HOST}:${DB_PORT}/${DB_NAME}
# Security
SECRET_KEY <str> = your-secret-key-here
ALLOWED_HOSTS <list> = localhost, 127.0.0.1, 0.0.0.0
CORS_ORIGINS <list> = http://localhost:3000, http://localhost:8080
# External Services
REDIS_URL <str> = redis://${HOST}:6379
EMAIL_CONFIG <json> = {"smtp_server": "smtp.gmail.com", "port": 587, "use_tls": true}
# Feature Flags
ENABLE_ANALYTICS <bool> = True
ENABLE_CACHING <bool> = True
MAINTENANCE_MODE <bool> = False
from envist import Envist
# Load configuration
env = Envist(path="config/web.env")
# Flask/FastAPI application setup
app_config = {
'name': env.get('APP_NAME'),
'version': env.get('APP_VERSION'),
'host': env.get('HOST'),
'port': env.get('PORT'),
'debug': env.get('DEBUG'),
'secret_key': env.get('SECRET_KEY'),
'database_url': env.get('DB_URL'),
'redis_url': env.get('REDIS_URL'),
'allowed_hosts': env.get('ALLOWED_HOSTS'),
'cors_origins': env.get('CORS_ORIGINS'),
'email_config': env.get('EMAIL_CONFIG'),
}
print(f"๐ Starting {app_config['name']} v{app_config['version']}")
print(f"๐ Server: {app_config['host']}:{app_config['port']}")
print(f"๐ Database: {app_config['database_url']}")
print(f"๐ง Email: {app_config['email_config']['smtp_server']}")
๐ฌ Data Science Project
# config/ml.env
PROJECT_NAME <str> = ML Pipeline
DATA_PATH <str> = ./data
MODEL_PATH <str> = ./models
OUTPUT_PATH <str> = ./outputs
# Data Processing
BATCH_SIZE <int> = 32
MAX_WORKERS <int> = 4
SAMPLE_RATE <float> = 0.1
RANDOM_SEED <int> = 42
# Model Configuration
MODEL_PARAMS <json> = {
"learning_rate": 0.001,
"epochs": 100,
"dropout": 0.2,
"hidden_layers": [128, 64, 32]
}
# Feature Engineering
SELECTED_FEATURES <list> = age, income, education, experience
CATEGORICAL_FEATURES <list> = gender, department, city
NUMERICAL_FEATURES <list> = age, income, experience, score
# Paths with expansion
TRAIN_DATA <str> = ${DATA_PATH}/train.csv
TEST_DATA <str> = ${DATA_PATH}/test.csv
MODEL_OUTPUT <str> = ${MODEL_PATH}/${PROJECT_NAME}_model.pkl
from envist import Envist
import pandas as pd
# Load ML configuration
env = Envist(path="config/ml.env")
# Training pipeline setup
config = {
'project_name': env.get('PROJECT_NAME'),
'batch_size': env.get('BATCH_SIZE'),
'max_workers': env.get('MAX_WORKERS'),
'sample_rate': env.get('SAMPLE_RATE'),
'random_seed': env.get('RANDOM_SEED'),
'model_params': env.get('MODEL_PARAMS'),
'selected_features': env.get('SELECTED_FEATURES'),
'categorical_features': env.get('CATEGORICAL_FEATURES'),
'numerical_features': env.get('NUMERICAL_FEATURES'),
'train_data_path': env.get('TRAIN_DATA'),
'test_data_path': env.get('TEST_DATA'),
'model_output_path': env.get('MODEL_OUTPUT'),
}
print(f"๐ฌ {config['project_name']} Configuration:")
print(f"๐ Batch size: {config['batch_size']}")
print(f"๐ฏ Model params: {config['model_params']}")
print(f"๐ Training data: {config['train_data_path']}")
๐ณ Docker & DevOps
# config/docker.env
ENVIRONMENT <str> = production
SERVICE_NAME <str> = my-api
VERSION <str> = 1.2.3
# Container Configuration
CONTAINER_PORT <int> = 8080
HOST_PORT <int> = 80
MEMORY_LIMIT <str> = 512m
CPU_LIMIT <float> = 0.5
# Registry
REGISTRY_URL <str> = your-registry.com
IMAGE_NAME <str> = ${REGISTRY_URL}/${SERVICE_NAME}:${VERSION}
# Health Check
HEALTH_CHECK_INTERVAL <int> = 30
HEALTH_CHECK_TIMEOUT <int> = 10
HEALTH_CHECK_RETRIES <int> = 3
HEALTH_CHECK_URL <str> = http://localhost:${CONTAINER_PORT}/health
# Environment Variables for Container
CONTAINER_ENV <json> = {
"NODE_ENV": "production",
"LOG_LEVEL": "info",
"DATABASE_URL": "postgresql://prod-db:5432/app"
}
from envist import Envist
# Load Docker configuration
env = Envist(path="config/docker.env")
# Generate Docker run command
def generate_docker_command():
image = env.get('IMAGE_NAME')
host_port = env.get('HOST_PORT')
container_port = env.get('CONTAINER_PORT')
memory = env.get('MEMORY_LIMIT')
cpu = env.get('CPU_LIMIT')
container_env = env.get('CONTAINER_ENV')
cmd_parts = [
"docker run -d",
f"--name {env.get('SERVICE_NAME')}",
f"-p {host_port}:{container_port}",
f"--memory={memory}",
f"--cpus={cpu}"
]
# Add environment variables
for key, value in container_env.items():
cmd_parts.append(f"-e {key}={value}")
cmd_parts.append(image)
return " ".join(cmd_parts)
print("๐ณ Docker Command:")
print(generate_docker_command())
๐งช Testing Configuration
# config/test.env
TEST_MODE <bool> = True
TEST_DATABASE <str> = sqlite:///:memory:
MOCK_EXTERNAL_APIS <bool> = True
# Test Data
TEST_DATA_PATH <str> = ./tests/data
FIXTURES_PATH <str> = ${TEST_DATA_PATH}/fixtures
EXPECTED_RESULTS_PATH <str> = ${TEST_DATA_PATH}/expected
# Performance Testing
LOAD_TEST_USERS <int> = 100
LOAD_TEST_DURATION <int> = 300
PERFORMANCE_THRESHOLDS <json> = {
"response_time_ms": 200,
"error_rate_percent": 1,
"throughput_rps": 1000
}
# Test Categories
UNIT_TESTS <list> = user, auth, payment, notification
INTEGRATION_TESTS <list> = api, database, cache
E2E_TESTS <list> = login_flow, purchase_flow, admin_flow
๐ค Contributing
We love contributions! ๐ Whether you're fixing bugs, adding features, or improving documentation, your help makes Envist better for everyone.
๐ Quick Start for Contributors
-
Fork the repository
git clone https://github.com/your-username/envist.git cd envist
-
Set up development environment
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate pip install -e ".[dev]"
-
Create your feature branch
git checkout -b feature/amazing-new-feature
-
Make your changes and test
# Run tests pytest # Check code style flake8 envist/ # Type checking mypy envist/
-
Commit and push
git commit -am "โจ Add amazing new feature" git push origin feature/amazing-new-feature
-
Create a Pull Request
- Go to GitHub and create a PR
- Describe your changes clearly
- Link any related issues
๐ฏ Areas We Need Help With
Area | Description | Difficulty |
---|---|---|
๐ Bug Fixes | Fix reported issues | Beginner |
๐ Documentation | Improve examples and guides | Beginner |
โจ New Data Types | Add support for more types | Intermediate |
๐ Multi-line Support | Enable multi-line values | Advanced |
โก Performance | Optimize parsing and loading | Advanced |
๐งช Testing | Increase test coverage | Intermediate |
๐ Development Guidelines
- Code Style: Follow PEP 8
- Testing: Write tests for new features
- Documentation: Update docs for changes
- Commit Messages: Use conventional commit format
- Backwards Compatibility: Don't break existing APIs
๐ Contributors
Thanks to all our amazing contributors! ๐
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
What does this mean?
โ
You can: Use, copy, modify, merge, publish, distribute, sublicense, and/or sell
โ
You must: Include the original copyright notice and license
โ We're not liable: The software is provided "as is" without warranty
TL;DR: Free to use for any purpose! ๐
Made with โค๏ธ by Md. Almas Ali
โญ Star this repo if Envist helped you! โญ
Report Bug โข Request Feature โข Documentation โข Examples
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
Built Distribution
File details
Details for the file envist-0.1.2.tar.gz
.
File metadata
- Download URL: envist-0.1.2.tar.gz
- Upload date:
- Size: 119.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.9.23
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
ac2aea3e796324ee048d89cf03ecbec1547c3e2ab615a758f9a0972c8f04b157
|
|
MD5 |
27b111df9cb5b55edc81f944517964be
|
|
BLAKE2b-256 |
a3ac3614cd6dae97c8d804a6eb1a87e6a1e31993b43cf0f8b530d60350a5a5e2
|
File details
Details for the file envist-0.1.2-py3-none-any.whl
.
File metadata
- Download URL: envist-0.1.2-py3-none-any.whl
- Upload date:
- Size: 34.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.9.23
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
4dfa6ff0c74e94d33ef357e32ac4bc230040ab0320b2e49bcbe21cb2c3800f02
|
|
MD5 |
72f5e6b3eeda68a5249f20dcb0d15b75
|
|
BLAKE2b-256 |
5f3bec3c722716eeb82ffb55c9e3d537cc0f4b15a0eef2cbf6eeeadc723d3347
|