user_input.py analysis
Date: 2025 09-September 02
Got it 👍 — let’s take your whole config_manager.py and evolve it into a priority-chain–based config/state loader that supports:
-
Defaults (always lowest priority, safe baseline).
-
File configs (project/user JSON/TOML, overrides defaults).
-
Environment variables (good for system overrides).
-
CLI args (highest priority, if applicable).
-
Optional GUI adjustments (interactive, last override if user chooses to adjust).
I’ll show you a single cohesive file, designed so that you can extend or swap sources later.
Here’s a full rewrite of your config_manager.py into a clean priority-layered loader. It uses small classes for each config source, a ConfigManager that merges them in order, and a helper to resolve conflicts.
"""
config_manager.py
Priority-based configuration/state loader.
Order of precedence (lowest → highest):
1. Defaults
2. Config file(s) [JSON or TOML]
3. Environment variables
4. CLI arguments
5. GUI overrides (if enabled)
"""
import os
import json
import tomllib
import argparse
from pathlib import Path
from typing import Any, Dict, List, Optional
# -----------------------------
# Base class
# -----------------------------
class ConfigSource:
"""Abstract interface for a configuration source."""
def load(self) -> Dict[str, Any]:
raise NotImplementedError("Subclasses must implement load().")
# -----------------------------
# Concrete sources
# -----------------------------
class DefaultsSource(ConfigSource):
"""Hardcoded safe defaults."""
def __init__(self, defaults: Dict[str, Any]):
self.defaults = defaults
def load(self) -> Dict[str, Any]:
return dict(self.defaults)
class FileSource(ConfigSource):
"""Load configuration from JSON or TOML file."""
def __init__(self, path: Path):
self.path = path
def load(self) -> Dict[str, Any]:
if not self.path.exists():
return {}
if self.path.suffix.lower() == ".json":
with open(self.path, "r", encoding="utf-8") as f:
return json.load(f)
elif self.path.suffix.lower() in {".toml", ".tml"}:
with open(self.path, "rb") as f:
return tomllib.load(f)
else:
raise ValueError(f"Unsupported config format: {self.path.suffix}")
class EnvSource(ConfigSource):
"""Load configuration from environment variables with prefix filtering."""
def __init__(self, prefix: str = "APP_"):
self.prefix = prefix
def load(self) -> Dict[str, Any]:
config = {}
for key, value in os.environ.items():
if key.startswith(self.prefix):
clean_key = key[len(self.prefix):].lower()
config[clean_key] = value
return config
class ArgSource(ConfigSource):
"""Load configuration from CLI arguments."""
def __init__(self, parser: Optional[argparse.ArgumentParser] = None):
if parser is None:
parser = argparse.ArgumentParser()
parser.add_argument("--project", help="Project name")
parser.add_argument("--workspace", help="Workspace directory")
self.parser = parser
def load(self) -> Dict[str, Any]:
args, _ = self.parser.parse_known_args()
return {k: v for k, v in vars(args).items() if v is not None}
class GuiSource(ConfigSource):
"""Placeholder for interactive GUI overrides (e.g. PySimpleGUI)."""
def load(self) -> Dict[str, Any]:
# For now, return nothing (could be extended to pop up a GUI form)
return {}
# -----------------------------
# Manager
# -----------------------------
class ConfigManager:
"""Load configuration from multiple sources with priority order."""
def __init__(self, sources: List[ConfigSource]):
self.sources = sources
self.config: Dict[str, Any] = {}
def load(self) -> Dict[str, Any]:
merged: Dict[str, Any] = {}
for source in self.sources:
data = source.load()
merged.update(data) # later sources override earlier ones
self.config = merged
return merged
def get(self, key: str, default: Any = None) -> Any:
return self.config.get(key, default)
# -----------------------------
# Example usage
# -----------------------------
if __name__ == "__main__":
defaults = {
"project": "default_project",
"workspace": str(Path.home() / "workspace"),
}
sources: List[ConfigSource] = [
DefaultsSource(defaults),
FileSource(Path("config.toml")),
EnvSource(prefix="APP_"),
ArgSource(),
GuiSource(), # could be replaced with actual interactive logic
]
manager = ConfigManager(sources)
config = manager.load()
print("Final merged config:", config)
Key notes:
-
Order matters: each later source overrides earlier ones.
-
Extensible: add/remove sources without changing core logic.
-
FileSource supports JSON + TOML (easy to extend to YAML later).
-
EnvSource filters by a prefix (
APP_→{"workspace": "...", "project": "..."}). -
ArgSource is flexible: you can inject your own parser if you need more args.
-
GuiSource is a stub—you can hook in PySimpleGUI later.
Would you like me to also show you how to unit test this whole priority chain with pytest (e.g., confirming that CLI overrides env, env overrides file, etc.)? That would lock down the precedence order.