Hone logo
Hone
Problems

Dynamic Environment Configuration in Python

Applications often need to behave differently based on their deployment environment (e.g., development, staging, production). This challenge requires you to build a robust system for managing these environment-specific configurations in Python. A well-implemented solution will make your application more adaptable and easier to deploy across various contexts.

Problem Description

Your task is to create a Python class or set of functions that can load and access configuration settings dynamically based on an active environment. The system should allow defining default configurations and overriding them with environment-specific settings.

Key Requirements:

  • Environment Identification: The system must be able to identify the current environment (e.g., 'development', 'staging', 'production'). This could be determined by an environment variable, a configuration file, or a default setting.
  • Configuration Loading: Load configuration values from at least two sources:
    • A default configuration (e.g., a Python dictionary).
    • Environment-specific overrides (e.g., separate Python dictionaries or JSON files for each environment).
  • Dynamic Access: Provide a mechanism to access configuration values using a clear, intuitive interface (e.g., attribute access or dictionary-like access).
  • Overriding Logic: Environment-specific configurations should take precedence over default configurations.
  • Error Handling: Gracefully handle cases where a requested configuration key is not found.

Expected Behavior:

The system should return the correct configuration value based on the active environment. If a value is not defined in the environment-specific configuration, it should fall back to the default configuration. If a key is not found in either, it should raise a clear error or return a defined default (e.g., None, or a custom exception).

Edge Cases to Consider:

  • What happens if the environment variable used for identification is not set?
  • How to handle nested configuration structures?
  • What if an environment-specific file is missing?

Examples

Example 1: Basic Environment Loading

# Assume current environment is 'development'
default_config = {
    "database": {
        "host": "localhost",
        "port": 5432,
        "user": "admin"
    },
    "api_key": "default_key",
    "debug_mode": True
}

development_config = {
    "database": {
        "host": "dev.db.local"
    },
    "debug_mode": False
}

# Initialize your environment handler with default and development configs
# Let's assume your handler is named 'ConfigHandler'
config = ConfigHandler(default_config, environments={'development': development_config})

print(config.database.host)
print(config.api_key)
print(config.debug_mode)

Output:

dev.db.local
default_key
False

Explanation:

The config.database.host is overridden by development_config. config.api_key falls back to the default_config as it's not in development_config. config.debug_mode is also overridden.

Example 2: Staging Environment

# Assume current environment is 'staging'
default_config = {
    "database": {
        "host": "localhost",
        "port": 5432,
        "user": "admin"
    },
    "api_key": "default_key",
    "debug_mode": True
}

staging_config = {
    "database": {
        "host": "staging.db.internal",
        "user": "staging_user"
    },
    "api_key": "staging_api_token"
}

# Initialize your environment handler with default and staging configs
config = ConfigHandler(default_config, environments={'staging': staging_config})

print(config.database.host)
print(config.database.user)
print(config.api_key)
print(config.debug_mode)

Output:

staging.db.internal
staging_user
staging_api_token
True

Explanation:

config.database.host and config.database.user are overridden by staging_config. config.api_key is also overridden. config.debug_mode is not defined in staging_config, so it falls back to the default_config.

Example 3: Missing Key and Nested Access

default_config = {
    "app": {
        "name": "My App",
        "version": "1.0.0"
    }
}

production_config = {
    "app": {
        "version": "1.1.0"
    }
}

# Assume current environment is 'production'
config = ConfigHandler(default_config, environments={'production': production_config})

print(config.app.name)
print(config.app.version)

# Attempting to access a non-existent key
try:
    print(config.database.host)
except AttributeError as e:
    print(f"Error: {e}")

Output:

My App
1.1.0
Error: 'ConfigHandler' object has no attribute 'database'

Explanation:

config.app.name uses the value from default_config. config.app.version is overridden by production_config. Accessing config.database.host raises an AttributeError because it's not found in either configuration.

Constraints

  • The system should be implemented in pure Python, without relying on external configuration management libraries like python-decouple or dynaconf.
  • The solution should handle nested dictionary structures for configurations.
  • The identification of the current environment should ideally be configurable (e.g., via an environment variable or a parameter to the handler).
  • Performance is not a critical concern for this challenge, but the solution should be reasonably efficient for typical application configurations.
  • The solution should be object-oriented, encapsulating the configuration logic within a class.

Notes

Consider how you will handle the mapping of environment names (e.g., 'development', 'staging') to their corresponding configuration data. You might use a dictionary where keys are environment names and values are the configuration dictionaries. Think about how to make accessing nested values seamless, perhaps by using __getattr__ or a similar mechanism. A clear and concise way to signal when a configuration value is missing is also important.

Loading editor...
python