Skip to main content

A Python library for managing hierarchical configuration files with profile-based inheritance and variable interpolation

Project description

Profile Config

Profile-based configuration management for Python applications.

What It Does

Profile Config manages application configuration using profiles (e.g., development, staging, production). It discovers configuration files in your project hierarchy, merges them with proper precedence, and resolves the requested profile.

Configuration Flow

1. Discovery Phase
   Search locations (highest to lowest precedence):
   ./myapp/config.yaml          <- Current directory
   ../myapp/config.yaml         <- Parent directory
   ../../myapp/config.yaml      <- Grandparent directory
   ~/myapp/config.yaml         <- Home directory

2. Merge Phase
   Files are merged with closer files taking precedence

3. Profile Resolution
   defaults + profile + inherited profiles

4. Override Phase
   Apply runtime overrides (highest precedence)

5. Interpolation Phase
   Resolve ${variable} references

Example

Given this configuration file at myapp/config.yaml:

defaults:
  host: localhost
  port: 5432
  debug: false

profiles:
  development:
    debug: true
    database: myapp_dev
    
  production:
    host: prod-db.example.com
    database: myapp_prod

This code:

from profile_config import ProfileConfigResolver

resolver = ProfileConfigResolver("myapp", profile="development")
config = resolver.resolve()

Produces this configuration:

{
    "host": "localhost",      # from defaults
    "port": 5432,             # from defaults
    "debug": True,            # from development profile (overrides defaults)
    "database": "myapp_dev"   # from development profile
}

Installation

pip install profile-config

For TOML support on Python < 3.11:

pip install profile-config[toml]

Basic Usage

1. Create Configuration File

Create myapp/config.yaml in your project:

defaults:
  timeout: 30
  retries: 3

profiles:
  development:
    debug: true
    log_level: DEBUG
    
  production:
    debug: false
    log_level: WARNING

2. Load Configuration

from profile_config import ProfileConfigResolver

# Load development profile
resolver = ProfileConfigResolver("myapp", profile="development")
config = resolver.resolve()

# Access configuration values
print(config["timeout"])    # 30 (from defaults)
print(config["debug"])      # True (from development profile)
print(config["log_level"])  # DEBUG (from development profile)

Configuration File Discovery

Profile Config searches for configuration files by walking up the directory tree from the current working directory, then checking the home directory.

Search Pattern

Default pattern: {config_name}/{profile_filename}.{extension}

Examples:

  • myapp/config.yaml (default)
  • myapp/settings.yaml (custom filename)
  • myapp/app.json (custom filename)

Search Order (highest to lowest precedence)

Current directory:     ./myapp/config.yaml
Parent directory:      ../myapp/config.yaml
Grandparent directory: ../../myapp/config.yaml
...
Home directory:        ~/myapp/config.yaml

File Extensions

Searches for files with these extensions (in order):

  • .yaml
  • .yml
  • .json
  • .toml

Example Directory Structure

/home/user/projects/myapp/
├── backend/
│   └── myapp/
│       └── config.yaml      <- Project-specific config
└── myapp/
    └── config.yaml          <- Shared config

/home/user/myapp/
└── config.yaml              <- User-specific config

When running from /home/user/projects/myapp/backend/:

  1. Finds ./myapp/config.yaml (current directory)
  2. Finds ../myapp/config.yaml (parent directory)
  3. Finds ~/myapp/config.yaml (home directory)
  4. Merges all three (current directory has highest precedence)

Custom Profile Filename

Use a different filename instead of config:

# Search for settings.yaml instead of config.yaml
resolver = ProfileConfigResolver(
    "myapp",
    profile="development",
    profile_filename="settings"
)

This searches for:

  • ./myapp/settings.yaml
  • ../myapp/settings.yaml
  • ~/myapp/settings.yaml

Use cases:

  • Organization naming standards (e.g., settings.yaml)
  • Multiple configuration types in same directory
  • Legacy system compatibility
  • More descriptive names (e.g., database.yaml, api.yaml)

Configuration File Format

Structure

# Optional: specify which profile to use by default
default_profile: development

# Optional: values applied to all profiles
defaults:
  timeout: 30
  retries: 3
  
# Required: profile definitions
profiles:
  development:
    debug: true
    database: myapp_dev
    
  production:
    debug: false
    database: myapp_prod

Supported Formats

YAML

defaults:
  host: localhost
  port: 5432

profiles:
  development:
    debug: true

JSON

{
  "defaults": {
    "host": "localhost",
    "port": 5432
  },
  "profiles": {
    "development": {
      "debug": true
    }
  }
}

TOML

[defaults]
host = "localhost"
port = 5432

[profiles.development]
debug = true

Profile Inheritance

Profiles can inherit from other profiles using the inherits key.

Example

profiles:
  base:
    host: localhost
    timeout: 30
    
  development:
    inherits: base
    debug: true
    database: myapp_dev
    
  staging:
    inherits: development
    debug: false
    host: staging-db.example.com

Resolution Order

For profile staging:

  1. Start with base profile
  2. Merge development profile (overrides base)
  3. Merge staging profile (overrides development)

Result:

{
    "host": "staging-db.example.com",  # from staging (overrides base)
    "timeout": 30,                      # from base
    "debug": False,                     # from staging (overrides development)
    "database": "myapp_dev"             # from development
}

Multi-Level Inheritance

profiles:
  base:
    timeout: 30
    
  development:
    inherits: base
    debug: true
    
  development-team1:
    inherits: development
    team_id: team1
    
  development-team2:
    inherits: development
    team_id: team2

Circular inheritance is detected and raises an error.

Variable Interpolation

Use ${variable} syntax to reference other configuration values.

Example

defaults:
  app_name: myapp
  base_path: /opt/${app_name}
  data_path: ${base_path}/data
  log_path: ${base_path}/logs

profiles:
  development:
    base_path: /tmp/${app_name}

Resolution

For profile development:

{
    "app_name": "myapp",
    "base_path": "/tmp/myapp",           # interpolated
    "data_path": "/tmp/myapp/data",      # interpolated
    "log_path": "/tmp/myapp/logs"        # interpolated
}

Variables are resolved after profile inheritance is complete.

Runtime Overrides

Apply configuration overrides at runtime with highest precedence.

Dictionary Override

resolver = ProfileConfigResolver(
    "myapp",
    profile="production",
    overrides={"debug": True, "host": "test-db.example.com"}
)
config = resolver.resolve()

File Override

resolver = ProfileConfigResolver(
    "myapp",
    profile="production",
    overrides="/path/to/overrides.yaml"
)
config = resolver.resolve()

Supported formats: .yaml, .yml, .json, .toml

List of Overrides

Apply multiple overrides in order (later overrides take precedence):

resolver = ProfileConfigResolver(
    "myapp",
    profile="production",
    overrides=[
        "/path/to/base-overrides.yaml",
        {"debug": True},
        "/path/to/final-overrides.json"
    ]
)
config = resolver.resolve()

Precedence Order

1. Discovered config files (lowest)
2. Profile resolution
3. Override 1
4. Override 2
5. Override N (highest)

Configuration Options

Customize Search Behavior

resolver = ProfileConfigResolver(
    config_name="myapp",
    profile="development",
    extensions=["yaml", "json"],  # Only search these formats
    search_home=False,            # Don't search home directory
)

Custom Profile Filename

# Use settings.yaml instead of config.yaml
resolver = ProfileConfigResolver(
    "myapp",
    profile="development",
    profile_filename="settings"
)

Custom Inheritance Key

# Use 'extends' instead of 'inherits'
resolver = ProfileConfigResolver(
    "myapp",
    profile="development",
    inherit_key="extends"
)

Disable Variable Interpolation

resolver = ProfileConfigResolver(
    "myapp",
    profile="development",
    enable_interpolation=False
)

Utility Methods

List Available Profiles

resolver = ProfileConfigResolver("myapp")
profiles = resolver.list_profiles()
print(profiles)  # ['development', 'staging', 'production']

Get Discovered Files

resolver = ProfileConfigResolver("myapp")
files = resolver.get_config_files()
for file_path in files:
    print(file_path)

Error Handling

Profile Config raises specific exceptions for different error conditions.

Exception Types

from profile_config.exceptions import (
    ConfigNotFoundError,      # No configuration files found
    ProfileNotFoundError,     # Requested profile doesn't exist
    CircularInheritanceError, # Circular inheritance detected
    ConfigFormatError         # Invalid configuration file format
)

Example

from profile_config import ProfileConfigResolver
from profile_config.exceptions import ProfileNotFoundError

try:
    resolver = ProfileConfigResolver("myapp", profile="nonexistent")
    config = resolver.resolve()
except ProfileNotFoundError as e:
    print(f"Profile not found: {e}")
    # Handle error (use default profile, exit, etc.)

Common Patterns

Environment-Based Configuration

import os
from profile_config import ProfileConfigResolver

env = os.environ.get("ENV", "development")
resolver = ProfileConfigResolver("myapp", profile=env)
config = resolver.resolve()

Team-Specific Configuration

profiles:
  development:
    debug: true
    
  development-team1:
    inherits: development
    team_id: team1
    endpoint: "https://team1.internal.com"
    
  development-team2:
    inherits: development
    team_id: team2
    endpoint: "https://team2.internal.com"
import os
from profile_config import ProfileConfigResolver

team = os.environ.get("TEAM_NAME", "")
env = os.environ.get("ENV", "development")
profile = f"{env}-{team}" if team else env

resolver = ProfileConfigResolver("myapp", profile=profile)
config = resolver.resolve()

Configuration with Secrets

Store secrets separately and merge at runtime:

import json
from pathlib import Path
from profile_config import ProfileConfigResolver

# Load base configuration
resolver = ProfileConfigResolver("myapp", profile="production")
config = resolver.resolve()

# Load secrets from secure location
secrets_file = Path("/etc/secrets/myapp.json")
if secrets_file.exists():
    with open(secrets_file) as f:
        secrets = json.load(f)
    config.update(secrets)

Or use overrides:

resolver = ProfileConfigResolver(
    "myapp",
    profile="production",
    overrides="/etc/secrets/myapp.json"
)
config = resolver.resolve()

Format Comparison

Feature YAML JSON TOML
Comments Yes No Yes
Multi-line strings Yes Escaped only Yes
Type safety Inferred Limited Native types
Nesting Natural Natural Verbose for deep nesting
Readability High Medium High
Ecosystem Mature Universal Growing

When to Use Each Format

YAML: Complex nested configurations, human-edited files
JSON: API integration, machine-generated configs, data exchange
TOML: Application configuration with type safety, flat structures

Examples

The examples/ directory contains working examples:

  • basic_usage.py - Basic configuration and profile usage
  • advanced_profiles.py - Inheritance patterns and error handling
  • web_app_config.py - Web application configuration management
  • toml_usage.py - TOML format features

Run examples:

cd examples
python basic_usage.py

Development

Setup

git clone https://github.com/bassmanitram/profile-config.git
cd profile-config
pip install -e ".[dev,toml]"

Run Tests

pytest

Run Tests with Coverage

pytest --cov=profile_config --cov-report=html

API Reference

ProfileConfigResolver

ProfileConfigResolver(
    config_name: str,
    profile: str = "default",
    profile_filename: str = "config",
    overrides: Optional[Union[Dict, PathLike, List[Union[Dict, PathLike]]]] = None,
    extensions: Optional[List[str]] = None,
    search_home: bool = True,
    inherit_key: str = "inherits",
    enable_interpolation: bool = True,
)

Parameters:

  • config_name: Name of configuration directory (e.g., "myapp")
  • profile: Profile name to resolve (default: "default")
  • profile_filename: Name of profile file without extension (default: "config")
  • overrides: Override values (dict, file path, or list of dicts/paths)
  • extensions: File extensions to search (default: ["yaml", "yml", "json", "toml"])
  • search_home: Whether to search home directory (default: True)
  • inherit_key: Key name for profile inheritance (default: "inherits")
  • enable_interpolation: Whether to enable variable interpolation (default: True)

Methods:

  • resolve() -> Dict[str, Any]: Resolve and return configuration
  • list_profiles() -> List[str]: List available profiles
  • get_config_files() -> List[Path]: Get discovered configuration files

License

MIT License. See LICENSE file for details.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md for guidelines.

Links

Changelog

See CHANGELOG.md for version history.

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

profile_config-1.1.0.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

profile_config-1.1.0-py3-none-any.whl (25.8 kB view details)

Uploaded Python 3

File details

Details for the file profile_config-1.1.0.tar.gz.

File metadata

  • Download URL: profile_config-1.1.0.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for profile_config-1.1.0.tar.gz
Algorithm Hash digest
SHA256 b9ac563fdf6ec1fc919a74c2bb0cf994858b3c30954a921fa36dce1e66d1eb8e
MD5 68d9f5e759e2c7bc9f05cf8cfa7c97a9
BLAKE2b-256 ad2c33ddefb5ed5331d6c9a11b3bb4fd772a8caf1920924a21906e03bc38f016

See more details on using hashes here.

Provenance

The following attestation bundles were made for profile_config-1.1.0.tar.gz:

Publisher: release.yml on bassmanitram/profile-config

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file profile_config-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: profile_config-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for profile_config-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 44de9a10dfa3fcaa1827d5b1ff9cff90a305b7fe902cc3f26e25777c4f811634
MD5 2cb762d247b786e0b30c103cd0861676
BLAKE2b-256 bbbf5b739197e2c007623188903f0211b17ec78170037d182e083f6bd0dfa729

See more details on using hashes here.

Provenance

The following attestation bundles were made for profile_config-1.1.0-py3-none-any.whl:

Publisher: release.yml on bassmanitram/profile-config

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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