Hone logo
Hone
Problems

Securely Accessing Application Settings with Environment Variables

Many applications need to load configuration settings like API keys, database credentials, or debugging flags. Hardcoding these sensitive values directly into your code is a security risk and makes deployment difficult. This challenge asks you to implement a mechanism to read these settings from environment variables, a standard and secure practice.

Problem Description

Your task is to create a Python function or class that can read configuration settings from environment variables. This mechanism should allow you to:

  • Define a set of expected configuration keys.
  • Attempt to retrieve the value for each key from the environment.
  • Handle cases where an environment variable is missing.
  • Optionally, provide default values for specific settings if they are not found in the environment.
  • Raise an error for critical settings that are absolutely required and not provided.

Key Requirements:

  • Reading from Environment: The solution must read values from actual environment variables (e.g., using os.environ).
  • Configuration Definition: You should be able to define the configuration schema, specifying which variables are expected, their types, and whether they have defaults or are mandatory.
  • Default Values: Support for providing default values if an environment variable is not set.
  • Mandatory Variables: Ability to designate certain variables as mandatory, raising an error if they are not found.
  • Type Conversion: Optionally, allow for basic type conversion (e.g., string to integer or boolean).

Expected Behavior:

The function/class should return a dictionary or object containing the loaded configuration. If a mandatory variable is missing, an appropriate exception should be raised. If a non-mandatory variable is missing and a default is provided, the default value should be used.

Edge Cases to Consider:

  • What happens if an environment variable exists but is an empty string?
  • How should boolean values be interpreted (e.g., "true", "false", "1", "0")?
  • What if a type conversion fails?

Examples

Example 1: Simple Retrieval with Mandatory Variable

# Assume the following environment variables are set:
# API_KEY="your_super_secret_api_key"
# DEBUG="false"

# Configuration definition:
# API_KEY: Mandatory string
# DEBUG: Optional boolean, default to False

# Code simulating the environment (for testing purposes)
import os
os.environ["API_KEY"] = "your_super_secret_api_key"
os.environ["DEBUG"] = "false"

# Your implementation would be called like this:
# config = load_config(settings={
#     "API_KEY": {"type": str, "required": True},
#     "DEBUG": {"type": bool, "default": False}
# })

# Expected Output (conceptual):
# {
#     "API_KEY": "your_super_secret_api_key",
#     "DEBUG": False
# }

Explanation: API_KEY is retrieved successfully. DEBUG is present, and its string value "false" is converted to the boolean False.

Example 2: Missing Optional Variable and Type Conversion Failure

# Assume the following environment variable is set:
# DATABASE_URL="postgres://user:pass@host:port/db"

# Configuration definition:
# DATABASE_URL: Mandatory string
# MAX_CONNECTIONS: Optional integer, default to 10
# LOG_LEVEL: Optional string, default to "INFO"

# Code simulating the environment (for testing purposes)
import os
os.environ["DATABASE_URL"] = "postgres://user:pass@host:port/db"
# MAX_CONNECTIONS is not set
# LOG_LEVEL is not set

# Your implementation would be called like this:
# config = load_config(settings={
#     "DATABASE_URL": {"type": str, "required": True},
#     "MAX_CONNECTIONS": {"type": int, "default": 10},
#     "LOG_LEVEL": {"type": str, "default": "INFO"}
# })

# Expected Output (conceptual):
# {
#     "DATABASE_URL": "postgres://user:pass@host:port/db",
#     "MAX_CONNECTIONS": 10,
#     "LOG_LEVEL": "INFO"
# }

Explanation: DATABASE_URL is retrieved. MAX_CONNECTIONS and LOG_LEVEL are missing, so their default values are used.

Example 3: Missing Mandatory Variable

# Assume NO environment variables related to the configuration are set.

# Configuration definition:
# SECRET_KEY: Mandatory string
# PORT: Optional integer, default to 8080

# Code simulating the environment (for testing purposes)
import os
# No environment variables are set for SECRET_KEY or PORT

# Your implementation would be called like this:
# config = load_config(settings={
#     "SECRET_KEY": {"type": str, "required": True},
#     "PORT": {"type": int, "default": 8080}
# })

# Expected Output (conceptual):
# Raises a ConfigurationError or similar exception with a message like:
# "Missing mandatory environment variable: SECRET_KEY"

Explanation: SECRET_KEY is mandatory and not found, so an error is raised. PORT would have used its default if the error hadn't occurred.

Constraints

  • The implementation should use Python's standard library, specifically os and potentially typing for type hints.
  • The solution should be efficient and not introduce significant overhead for loading configuration.
  • Type conversion should be limited to basic Python types like str, int, float, and bool. Handling custom types is out of scope for this challenge.
  • Error messages for missing mandatory variables should be clear and informative.

Notes

Consider how you will handle parsing boolean values. A common approach is to treat strings like "true", "yes", "1" as True and "false", "no", "0" as False (case-insensitive).

Think about a good way to structure the configuration definition. A dictionary where keys are environment variable names and values are dictionaries describing their properties (type, required, default) is a good starting point.

You might want to create a custom exception class for configuration-related errors.

Loading editor...
python