Files
markitect-main/tddai/config.py
tegwick a7a7960ef6 feat: Implement unified configuration management system
Consolidates scattered configuration patterns across TDDAI, Gitea, and
MarkiTect into a unified, maintainable system addressing issue #22.

Key improvements:
- Created centralized config/ module with base classes and utilities
- Eliminated duplicate load_dotenv_file() functions
- Standardized environment variables with MARKITECT_ prefix
- Implemented comprehensive validation with helpful error messages
- Maintained full backward compatibility with existing TDDAI config

Architecture:
- BaseConfig: Abstract base with common functionality
- MarkitectConfig: Main configuration class with legacy support
- Compatibility layer: TddaiConfigCompat and GiteaConfigCompat wrappers
- Unified error handling: ConfigurationError hierarchy

All existing tests pass without modification, ensuring seamless transition.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 17:45:56 +02:00

171 lines
5.0 KiB
Python

"""
Configuration management for tddai.
DEPRECATED: This module is kept for backward compatibility only.
New code should use the unified configuration system in the `config` module.
The tddai framework is project-agnostic and can be configured per project
via environment variables:
- TDDAI_WORKSPACE_DIR: Workspace directory name (default: .tddai_workspace)
- TDDAI_GITEA_URL: Git platform URL
- TDDAI_REPO_OWNER: Repository owner/organization
- TDDAI_REPO_NAME: Repository name
Example .env file for a project:
```
TDDAI_WORKSPACE_DIR=.myproject_workspace
TDDAI_GITEA_URL=https://github.com
TDDAI_REPO_OWNER=myusername
TDDAI_REPO_NAME=myproject
```
"""
import os
from pathlib import Path
from typing import Optional
from dataclasses import dataclass
from .exceptions import ConfigurationError
# Import unified configuration system
try:
from config import get_tddai_config as _get_unified_tddai_config
_UNIFIED_CONFIG_AVAILABLE = True
except ImportError:
_UNIFIED_CONFIG_AVAILABLE = False
def load_dotenv_file(env_file: Path) -> None:
"""Load environment variables from a .env file.
DEPRECATED: Use config.loaders.load_env_file() instead.
"""
if not env_file.exists():
return
with open(env_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ.setdefault(key.strip(), value.strip())
@dataclass
class TddaiConfig:
"""Configuration settings for tddai.
DEPRECATED: Use config.TddaiConfigCompat instead.
"""
# Workspace settings
workspace_dir: Path = Path(".tddai_workspace")
current_issue_file: str = "current_issue.json"
# Git repository settings (must be configured per project)
gitea_url: str = ""
repo_owner: str = ""
repo_name: str = ""
# Test settings
tests_dir: Path = Path("tests")
test_file_pattern: str = "test_issue_{issue_num}_{scenario}.py"
# AI settings
claude_code_command: str = "claude"
@property
def issues_api_url(self) -> str:
"""Get the full issues API URL."""
return f"{self.gitea_url}/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues"
@property
def current_issue_path(self) -> Path:
"""Get the path to current issue file."""
return self.workspace_dir / self.current_issue_file
@classmethod
def from_environment(cls) -> "TddaiConfig":
"""Create config from environment variables and .env files."""
# Auto-load .env.tddai file if it exists
env_file = Path(".env.tddai")
load_dotenv_file(env_file)
config = cls()
# Override with environment variables if present
gitea_url = os.getenv("TDDAI_GITEA_URL")
if gitea_url:
config.gitea_url = gitea_url
repo_owner = os.getenv("TDDAI_REPO_OWNER")
if repo_owner:
config.repo_owner = repo_owner
repo_name = os.getenv("TDDAI_REPO_NAME")
if repo_name:
config.repo_name = repo_name
workspace_dir = os.getenv("TDDAI_WORKSPACE_DIR")
if workspace_dir:
config.workspace_dir = Path(workspace_dir)
return config
def validate(self) -> None:
"""Validate configuration settings."""
if not self.gitea_url:
raise ConfigurationError("gitea_url cannot be empty")
if not self.repo_owner:
raise ConfigurationError("repo_owner cannot be empty")
if not self.repo_name:
raise ConfigurationError("repo_name cannot be empty")
# Global config instance
_config: Optional[TddaiConfig] = None
def get_config() -> TddaiConfig:
"""Get the global configuration instance.
DEPRECATED: Use config.get_tddai_config() instead for new code.
This function maintains backward compatibility.
"""
global _config
if _config is None:
if _UNIFIED_CONFIG_AVAILABLE:
# Use unified configuration system if available
try:
unified_config = _get_unified_tddai_config()
_config = TddaiConfig(
workspace_dir=unified_config.workspace_dir,
gitea_url=unified_config.gitea_url,
repo_owner=unified_config.repo_owner,
repo_name=unified_config.repo_name,
tests_dir=unified_config.tests_dir,
test_file_pattern=unified_config.test_file_pattern,
claude_code_command=unified_config.claude_code_command
)
return _config
except Exception:
# Fall back to legacy behavior if unified config fails
pass
# Legacy fallback
_config = TddaiConfig.from_environment()
_config.validate()
return _config
def set_config(config: TddaiConfig) -> None:
"""Set the global configuration instance.
DEPRECATED: Use the unified configuration system instead.
"""
global _config
config.validate()
_config = config