EnvironmentsΒΆ

Choosing between development, testing, and production environments via environment variables.

(Example courtesy of Anthropic claude-3-7-sonnet.)

# ruff: noqa: F821,PLR2004


import os
import sys
import random
from enum import Enum


import ictruck


class Environment( Enum ):
    DEV = 'development'
    TEST = 'testing'
    PROD = 'production'


def main( ):
    env_name = os.environ.get( 'APP_ENV', 'development' ).lower( )
    try: env = next( ev for ev in Environment if ev.value == env_name )
    except StopIteration:
        print(
            f"Warning: Unknown environment '{env_name}', "
            "defaulting to development", file = sys.stderr )
        env = Environment.DEV

    flavors: dict[ ictruck.Flavor, ictruck.FlavorConfiguration ] = {
        'api': ictruck.FlavorConfiguration( prefix_emitter = 'API| ' ),
        'db': ictruck.FlavorConfiguration( prefix_emitter = 'DATABASE| ' ),
        'auth': ictruck.FlavorConfiguration( prefix_emitter = 'AUTH| ' ),
        'cache': ictruck.FlavorConfiguration( prefix_emitter = 'CACHE| ' ),
        'ui': ictruck.FlavorConfiguration( prefix_emitter = 'UI| ' ),
        'error': ictruck.FlavorConfiguration( prefix_emitter = 'ERROR| ' ),
        'warning': ictruck.FlavorConfiguration( prefix_emitter = 'WARNING| ' ),
        'notice': ictruck.FlavorConfiguration( prefix_emitter = 'NOTICE| ' ),
        **ictruck.produce_default_flavors( ),
    }

    trace_levels = -1  # Default to disabled
    active_flavors = [ ]

    match env:
        case Environment.DEV:
            # Development: Full debugging
            trace_levels = 9  # All trace levels
            active_flavors = [
                'api', 'db', 'auth', 'cache', 'ui',
                'error', 'warning', 'notice' ]
        case Environment.TEST:
            # Testing: Moderate debugging
            trace_levels = 2  # Only important traces
            active_flavors = [ 'api', 'db', 'auth', 'error', 'warning' ]
        case Environment.PROD:
            # Production: Minimal debugging (errors only)
            trace_levels = 0  # Only critical traces
            active_flavors = [ 'error' ]

    # Allow trace levels to be overridden by environment variable
    if 'DEBUG_LEVEL' in os.environ:
        try: trace_levels = int( os.environ[ 'DEBUG_LEVEL' ] )
        except ValueError:
            print(
                f"Warning: Invalid DEBUG_LEVEL '{os.environ['DEBUG_LEVEL']}', "
                "using default", file = sys.stderr )
    # Allow active flavors to be overridden by environment variable
    if 'DEBUG_FLAVORS' in os.environ:
        override_flavors = [
            f.strip( ) for f in os.environ[ 'DEBUG_FLAVORS' ].split( "," ) ]
        # Only use flavors that are registered
        active_flavors = [ f for f in override_flavors if f in flavors ]

    generalcfg = ictruck.VehicleConfiguration( flavors = flavors )
    ictruck.install(
        active_flavors = active_flavors,
        generalcfg = generalcfg,
        trace_levels = trace_levels )

    run_application( )


def run_application( ):
    ''' Simulates application with various debug outputs. '''
    ictr( 0 )( "Application starting" )
    # API calls (level 1 trace)
    ictr( 1 )( "Loading configuration" )
    api_call( "/api/config", "GET" )
    # Database operations (level 2 trace)
    ictr( 2 )( "Initializing database connections" )
    db_query( "SELECT version()" )
    # Detailed operations (level 3 trace)
    ictr( 3 )( "Detailed initialization steps" )
    # Named flavors for specific subsystems
    ictr( 'api' )( "API subsystem initialized" )
    ictr( 'db' )( "Database connections established" )
    ictr( 'auth' )( "Authentication service ready" )
    # Always show error messages
    ictr( 'error' )( "This error message should appear even in production" )
    # Simulate a user operation with multiple debug levels
    process_user_operation( )
    # Application shutdown
    ictr( 0 )( "Application shutting down" )


def api_call( endpoint, method, data = None ):
    ''' Simulates API call with debug tracking. '''
    ictr( 'api' )( method, endpoint )
    # Simulate success/failure
    if random.random( ) < 0.9: # noqa: S311
        ictr( 'api' )( "API call successful" )
        return { 'status': 'success' }
    ictr( 'warning' )( "API call failed" )
    return { 'status': 'error' }


def db_query( query ):
    ''' Simulates database query with debug tracking. '''
    ictr( 'db' )( query )
    ictr( 3 )( "Establishing connection pool" )
    ictr( 3 )( "Preparing statement" )
    # Success/failure simulation
    if random.random( ) < 0.95: # noqa: S311
        ictr( 'db' )( "Query executed successfully" )
        return [ { 'result': 'Sample data' } ]
    ictr( 'error' )( "Database query failed" )
    return None


def process_user_operation( ):
    ''' Simulates user operation with various debug outputs. '''
    ictr( 1 )( "Processing user operation." )
    ictr( 'auth' )( "Verifying user credentials" )
    ictr( 'cache' )( "Checking cache for data" )
    ictr( 2 )( "Creating user context." )
    ictr( 2 )( "Validating input data." )
    # Simulate error condition
    if random.random( ) < 0.2: # noqa: S311
        ictr( 'error' )( "Invalid user input detected." )
    ictr( 'ui' )( "Updating user interface." )
    ictr( 'notice' )( "User operation completed." )


if __name__ == '__main__': main( )

Running this will result in the following (or something similar):

TRACE0| 'Application starting'
TRACE1| 'Loading configuration'
API| method: 'GET', endpoint: '/api/config'
API| f"API call successful": 'API call successful'
TRACE2| 'Initializing database connections'
DATABASE| query: 'SELECT version()'
TRACE3| 'Establishing connection pool'
TRACE3| 'Preparing statement'
DATABASE| 'Query executed successfully'
TRACE3| 'Detailed initialization steps'
API| 'API subsystem initialized'
DATABASE| 'Database connections established'
AUTH| 'Authentication service ready'
ERROR| 'This error message should appear even in production'
TRACE1| 'Processing user operation.'
AUTH| 'Verifying user credentials'
CACHE| 'Checking cache for data'
TRACE2| 'Creating user context.'
TRACE2| 'Validating input data.'
ERROR| 'Invalid user input detected.'
UI| 'Updating user interface.'
NOTICE| 'User operation completed.'
TRACE0| 'Application shutting down'

Running this with APP_ENV set to testing will result in the following (or something similar):

TRACE0| 'Application starting'
TRACE1| 'Loading configuration'
API| method: 'GET', endpoint: '/api/config'
API| f"API call successful": 'API call successful'
TRACE2| 'Initializing database connections'
DATABASE| query: 'SELECT version()'
DATABASE| 'Query executed successfully'
API| 'API subsystem initialized'
DATABASE| 'Database connections established'
AUTH| 'Authentication service ready'
ERROR| 'This error message should appear even in production'
TRACE1| 'Processing user operation.'
AUTH| 'Verifying user credentials'
TRACE2| 'Creating user context.'
TRACE2| 'Validating input data.'
ERROR| 'Invalid user input detected.'
TRACE0| 'Application shutting down'

And, running this with APP_ENV set to prod will result in the following (or something similar):

TRACE0| 'Application starting'
ERROR| 'This error message should appear even in production'
TRACE0| 'Application shutting down'