Type Detection

One of envdot’s key features is automatic type detection. Instead of returning everything as strings like os.getenv(), envdot intelligently converts values to their appropriate Python types.

How It Works

When loading environment variables, envdot analyzes each value and converts it to the most appropriate Python type:

from envdot import DotEnv

# Given this .env file:
# DEBUG=true
# PORT=8080
# TIMEOUT=30.5
# APP_NAME=MyApp
# EMPTY_VALUE=

env = DotEnv('.env')

env.get('DEBUG')       # Returns: True (bool)
env.get('PORT')        # Returns: 8080 (int)
env.get('TIMEOUT')     # Returns: 30.5 (float)
env.get('APP_NAME')    # Returns: 'MyApp' (str)
env.get('EMPTY_VALUE') # Returns: None

Type Detection Rules

envdot uses the following rules for automatic type detection:

Boolean Values

The following strings are converted to True:

  • true (case-insensitive)

  • yes (case-insensitive)

  • on (case-insensitive)

  • 1

The following strings are converted to False:

  • false (case-insensitive)

  • no (case-insensitive)

  • off (case-insensitive)

  • 0

# All of these become True
DEBUG=true
DEBUG=True
DEBUG=TRUE
DEBUG=yes
DEBUG=on
DEBUG=1

# All of these become False
DEBUG=false
DEBUG=False
DEBUG=no
DEBUG=off
DEBUG=0

None Values

The following are converted to None:

  • none (case-insensitive)

  • null (case-insensitive)

  • Empty string

EMPTY_VAR=
NULL_VAR=null
NONE_VAR=none

Integer Values

Strings containing only digits (with optional leading minus sign) are converted to integers:

PORT=8080        # 8080 (int)
MAX_CONN=100     # 100 (int)
OFFSET=-10       # -10 (int)
ZERO=0           # Note: "0" becomes False (bool), not 0 (int)

Float Values

Strings containing digits with a decimal point are converted to floats:

TIMEOUT=30.5     # 30.5 (float)
RATE=0.15        # 0.15 (float)
TEMP=-3.14       # -3.14 (float)

String Values

Everything that doesn’t match the above patterns remains as a string:

APP_NAME=MyApp              # 'MyApp' (str)
URL=https://example.com     # 'https://example.com' (str)
VERSION=1.0.0               # '1.0.0' (str) - not a valid float
MIXED=abc123                # 'abc123' (str)

Explicit Type Casting

You can override automatic detection with explicit type casting:

env = DotEnv('.env')

# Force string type (even for numbers)
version = env.get('PORT', cast_type=str)  # '8080' (str)

# Force integer type
count = env.get('COUNT', cast_type=int)

# Force boolean type
enabled = env.get('ENABLED', cast_type=bool)

# Force float type
rate = env.get('RATE', cast_type=float)

List Values

Anything that matches the above pattern will remain a string unless a specific cast_type is specified:

ALLOWED_HOST=*,127.0.0.1 192.168.10.2,example.com  # (str)
# example:
os.getenv('ALLOWED_HOST', cast_type=list)
# [*,127.0.0.1,192.168.10.2,example.com]  # (list)

Tuple Values

Anything that matches the above pattern will remain a string unless a specific cast_type is specified:

ALLOWED_HOST=*,127.0.0.1 192.168.10.2,example.com  # (str)
# example:
os.getenv('ALLOWED_HOST', cast_type=tuple)
# (*,127.0.0.1,192.168.10.2,example.com)  # (tuple)

Type Conversion Errors

If explicit casting fails, a TypeConversionError is raised:

from envdot import DotEnv
from envdot.exceptions import TypeConversionError

env = DotEnv('.env')
# APP_NAME=MyApplication

try:
    value = env.get('APP_NAME', cast_type=int)
except TypeConversionError as e:
    print(f"Cannot convert: {e}")

Using Helper Functions

envdot provides type-specific helper functions:

from envdot import (
    getenv_typed,
    getenv_int,
    getenv_bool,
    getenv_float,
    getenv_str
)

# Auto-detect type
port = getenv_typed('PORT')  # Returns appropriate type

# Specific type getters
port = getenv_int('PORT', default=8000)
debug = getenv_bool('DEBUG', default=False)
timeout = getenv_float('TIMEOUT', default=30.0)
name = getenv_str('APP_NAME', default='MyApp')

Comparison with os.getenv

Standard os.getenv() always returns strings:

import os

os.environ['PORT'] = '8080'
os.environ['DEBUG'] = 'true'

# Standard behavior - always strings
port = os.getenv('PORT')     # '8080' (str)
debug = os.getenv('DEBUG')   # 'true' (str)

# Manual conversion needed
port = int(os.getenv('PORT'))
debug = os.getenv('DEBUG').lower() == 'true'

With envdot, this becomes much cleaner:

from envdot import load_env, get_env

load_env()

# Automatic type detection
port = get_env('PORT')   # 8080 (int)
debug = get_env('DEBUG') # True (bool)

Patching os Module

For seamless integration, you can patch the os module:

from envdot import patch_os_module

patch_os_module()

import os

# Now os has typed getters
port = os.getenv_typed('PORT')
debug = os.getenv_bool('DEBUG')
timeout = os.getenv_float('TIMEOUT')

# Set with type preservation
os.setenv_typed('NEW_PORT', 9000)

TypeDetector Class

For advanced use cases, you can use the TypeDetector class directly:

from envdot.core import TypeDetector

# Detect and convert value
value = TypeDetector.auto_detect('true')    # True (bool)
value = TypeDetector.auto_detect('8080')    # 8080 (int)
value = TypeDetector.auto_detect('30.5')    # 30.5 (float)

# Convert back to string
string = TypeDetector.to_string(True)       # 'true'
string = TypeDetector.to_string(8080)       # '8080'
string = TypeDetector.to_string(30.5)       # '30.5'
string = TypeDetector.to_string(None)       # ''

Best Practices

  1. Use meaningful boolean values: Prefer true/false over 1/0

  2. Be explicit when needed: Use cast_type for ambiguous values

  3. Handle None carefully: Empty values become None, not empty strings

  4. Version numbers: Keep as strings (VERSION=1.0.0) to avoid float issues

  5. Test type detection: Verify your values are detected as expected