Path Expansion
utilityhub_config provides utilities to automatically expand paths with tilde (~) and environment variables in your configuration files. This is especially useful for cross-platform applications where paths should be resolved at runtime.
Overview
Path expansion supports:
- Tilde expansion (
~): Expands to the user's home directory ~/config/app.yaml→/home/username/config/app.yaml- Environment variables (
$VARor${VAR}): Expands environment variable references $HOME/config→/home/username/config${CONFIG_DIR}/app.yaml→/etc/myapp/app.yaml- Path validation: Optionally validate that paths exist after expansion
Utility Functions
expand_path(path: str) -> Path
Expand a path string without validation.
from utilityhub_config import expand_path
# Tilde expansion
expanded = expand_path("~/config/app.yaml")
print(expanded) # PosixPath('/home/username/config/app.yaml')
# Environment variable expansion
expanded = expand_path("$CONFIG_DIR/app.yaml")
print(expanded) # PosixPath('/etc/myapp/app.yaml')
# Combined expansion
expanded = expand_path("~/$APP_NAME/config.toml")
expand_and_validate_path(path: str) -> Path
Expand a path and validate that it exists.
from utilityhub_config import expand_and_validate_path
# Raises FileNotFoundError if path doesn't exist
config_path = expand_and_validate_path("~/config/app.yaml")
print(config_path) # PosixPath('/home/username/config/app.yaml')
Using with Pydantic Models
For automatic path expansion in your configuration models, use the expand_path_validator function with Pydantic's field validator decorator:
Example: Basic Path Field
from pathlib import Path
from pydantic import BaseModel, field_validator
from utilityhub_config import load_settings, expand_path_validator
class Config(BaseModel):
config_file: Path
log_dir: Path
@field_validator("config_file", "log_dir", mode="before")
@classmethod
def expand_paths(cls, v: Path | str) -> Path:
return expand_path_validator(v)
# Load configuration
settings, _ = load_settings(Config, app_name="myapp")
Configuration File with Expanded Paths
myapp.toml:
config_file = "~/.config/myapp/app.toml"
log_dir = "$LOG_ROOT/myapp"
At runtime, these will be expanded to absolute paths.
Cross-Platform Usage
Path expansion works consistently across platforms:
Unix/Linux/macOS:
~ → /home/username
$HOME → /home/username
Windows:
~ → C:\Users\username
%USERPROFILE% → C:\Users\username
Environment variable expansion uses the system's native conventions.
Examples
Example 1: Database Configuration
from pathlib import Path
from pydantic import BaseModel, field_validator
from utilityhub_config import load_settings, expand_path_validator
class DatabaseConfig(BaseModel):
db_file: Path
backup_dir: Path
@field_validator("db_file", "backup_dir", mode="before")
@classmethod
def expand_db_paths(cls, v: Path | str) -> Path:
return expand_path_validator(v)
# config.yaml:
# db_file: ~/myapp/data.db
# backup_dir: $BACKUP_ROOT/myapp
settings, _ = load_settings(DatabaseConfig, app_name="myapp")
print(settings.db_file) # /home/user/myapp/data.db
print(settings.backup_dir) # /var/backups/myapp
Example 2: Logging Configuration
from pathlib import Path
from pydantic import BaseModel, field_validator
from utilityhub_config import load_settings, expand_path_validator
class LogConfig(BaseModel):
log_file: Path
error_log: Path
@field_validator("log_file", "error_log", mode="before")
@classmethod
def expand_log_paths(cls, v: Path | str) -> Path:
return expand_path_validator(v)
# .env:
# LOG_FILE=~/logs/app.log
# ERROR_LOG=$LOG_DIR/errors.log
settings, _ = load_settings(LogConfig)
Example 3: Mixed Configuration Sources
from pathlib import Path
from pydantic import BaseModel, field_validator
from utilityhub_config import load_settings, expand_path_validator
class AppConfig(BaseModel):
config_dir: Path
data_dir: Path
credentials_file: Path
@field_validator("config_dir", "data_dir", "credentials_file", mode="before")
@classmethod
def expand_all_paths(cls, v: Path | str) -> Path:
return expand_path_validator(v)
# Priority order (from lowest to highest):
# 1. app.yaml: data_dir = ~/data
# 2. Environment: DATA_DIR=/var/lib/myapp
# 3. Runtime override: credentials_file=/secure/creds.yaml
settings, meta = load_settings(
AppConfig,
app_name="myapp",
overrides={"credentials_file": "/secure/creds.yaml"}
)
Error Handling
When a path doesn't exist after expansion, a FileNotFoundError is raised:
from utilityhub_config import expand_and_validate_path
from pathlib import Path
from pydantic import BaseModel, field_validator, ValidationError
from utilityhub_config import load_settings, expand_path_validator
class Config(BaseModel):
config_file: Path
@field_validator("config_file", mode="before")
@classmethod
def expand_config(cls, v: Path | str) -> Path:
return expand_path_validator(v)
try:
# This will fail if ~/nonexistent.yaml doesn't exist
settings, _ = load_settings(
Config,
overrides={"config_file": "~/nonexistent.yaml"}
)
except ValidationError as e:
print(f"Configuration error: {e}")
Tips & Best Practices
1. Validate Paths in Development
Always test your path configurations to ensure they resolve correctly:
from utilityhub_config import expand_path
config_path = expand_path("~/myapp/config.yaml")
print(f"Config will be loaded from: {config_path}")
2. Use Environment Variables for Flexibility
Instead of hardcoding paths, use environment variables:
# config.toml (good)
data_dir = "$DATA_ROOT/myapp"
# config.toml (avoid)
data_dir = "/var/lib/myapp"
3. Set Environment Variables in Startup Scripts
Ensure environment variables are available where your application runs:
#!/bin/bash
export LOG_ROOT="/var/log"
export DATA_ROOT="/var/lib"
python -m myapp
4. Document Path Expectations
In your application's README or configuration guide, document what paths are used:
## Configuration
- `config_file`: Primary configuration file (required)
- `log_dir`: Directory for application logs
- `data_dir`: Directory for application data storage
Supports tilde (`~`) and environment variables.