Skip to main content

A simple .env file parser with types for Python

Project description

๐ŸŒŸ Envist

Envist Logo

The most powerful environment variable manager for Python ๐Ÿš€

PyPI version PyPI - Python Version PyPI - License Hits Code Coverage Downloads

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?

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 name
default: Fallback value
cast: 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 name
value: Variable value
cast: 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 spaces
sort_keys: Sort alphabetically
example_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 against
  • key (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

  1. Fork the repository

    git clone https://github.com/your-username/envist.git
    cd envist
    
  2. Set up development environment

    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
    pip install -e ".[dev]"
    
  3. Create your feature branch

    git checkout -b feature/amazing-new-feature
    
  4. Make your changes and test

    # Run tests
    pytest
    
    # Check code style
    flake8 envist/
    
    # Type checking
    mypy envist/
    
  5. Commit and push

    git commit -am "โœจ Add amazing new feature"
    git push origin feature/amazing-new-feature
    
  6. 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

envist-0.1.2.tar.gz (119.9 kB view details)

Uploaded Source

Built Distribution

envist-0.1.2-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

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

Hashes for envist-0.1.2.tar.gz
Algorithm Hash digest
SHA256 ac2aea3e796324ee048d89cf03ecbec1547c3e2ab615a758f9a0972c8f04b157
MD5 27b111df9cb5b55edc81f944517964be
BLAKE2b-256 a3ac3614cd6dae97c8d804a6eb1a87e6a1e31993b43cf0f8b530d60350a5a5e2

See more details on using hashes here.

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

Hashes for envist-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4dfa6ff0c74e94d33ef357e32ac4bc230040ab0320b2e49bcbe21cb2c3800f02
MD5 72f5e6b3eeda68a5249f20dcb0d15b75
BLAKE2b-256 5f3bec3c722716eeb82ffb55c9e3d537cc0f4b15a0eef2cbf6eeeadc723d3347

See more details on using hashes here.

Supported by

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