- Fix issue manager to properly read API token and repo info from main MarkiTect config - Update Gitea plugin to use correct repository-specific API endpoints - Correct domain model mapping to only include valid Issue model fields - Fix presentation layer to safely access optional body attribute - Enable full functionality for 'markitect issues show' and 'markitect issues list' commands 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
"""
|
|
Plugin manager for issue backends.
|
|
|
|
This module handles discovery, loading, and configuration of issue management backends.
|
|
"""
|
|
|
|
import importlib
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Type, Optional
|
|
|
|
from .base import IssueBackend
|
|
from .exceptions import PluginNotFoundError, ConfigurationError
|
|
|
|
|
|
class IssuePluginManager:
|
|
"""Manages issue backend plugins and configuration."""
|
|
|
|
def __init__(self, config_path: Optional[str] = None):
|
|
"""
|
|
Initialize plugin manager.
|
|
|
|
Args:
|
|
config_path: Optional path to configuration file
|
|
"""
|
|
self.config = self._load_config(config_path)
|
|
self.plugins = self._discover_plugins()
|
|
|
|
def get_backend(self, backend_name: Optional[str] = None) -> IssueBackend:
|
|
"""
|
|
Get configured backend instance.
|
|
|
|
Args:
|
|
backend_name: Backend name to use, or None for default
|
|
|
|
Returns:
|
|
IssueBackend instance
|
|
|
|
Raises:
|
|
PluginNotFoundError: If backend not found
|
|
"""
|
|
backend_name = backend_name or self.config.get('default_backend', 'gitea')
|
|
|
|
plugin_class = self.plugins.get(backend_name)
|
|
if not plugin_class:
|
|
raise PluginNotFoundError(f"Unknown backend: {backend_name}")
|
|
|
|
backend_config = self.config.get('backends', {}).get(backend_name, {})
|
|
return plugin_class(backend_config)
|
|
|
|
def _load_config(self, config_path: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Load configuration from file or return defaults.
|
|
|
|
Args:
|
|
config_path: Path to configuration file
|
|
|
|
Returns:
|
|
Configuration dictionary
|
|
"""
|
|
from config.manager import UnifiedConfigManager
|
|
|
|
# Get main markitect configuration to extract API token and settings
|
|
try:
|
|
config_manager = UnifiedConfigManager()
|
|
markitect_config = config_manager.get_config()
|
|
main_config = markitect_config.__dict__ if hasattr(markitect_config, '__dict__') else {}
|
|
except Exception:
|
|
main_config = {}
|
|
|
|
if config_path is None:
|
|
config_path = Path('.markitect/config/issues.yml')
|
|
else:
|
|
config_path = Path(config_path)
|
|
|
|
# Extract configuration from main markitect config
|
|
gitea_url = main_config.get('gitea_url', 'http://92.205.130.254:32166')
|
|
api_token = main_config.get('api_token', '')
|
|
repo_owner = main_config.get('repo_owner', 'coulomb')
|
|
repo_name = main_config.get('repo_name', 'markitect_project')
|
|
|
|
# Default configuration using main config values
|
|
default_config = {
|
|
'default_backend': 'gitea',
|
|
'backends': {
|
|
'gitea': {
|
|
'url': gitea_url,
|
|
'token': api_token,
|
|
'repo_owner': repo_owner,
|
|
'repo_name': repo_name,
|
|
'repo': f'{repo_owner}/{repo_name}'
|
|
},
|
|
'local': {
|
|
'directory': '.markitect/issues',
|
|
'auto_git': True
|
|
}
|
|
}
|
|
}
|
|
|
|
if not config_path.exists():
|
|
return default_config
|
|
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
config = yaml.safe_load(f) or {}
|
|
|
|
# Merge with defaults
|
|
merged_config = default_config.copy()
|
|
merged_config.update(config)
|
|
|
|
# Ensure gitea backend has token from main config if not overridden
|
|
if 'backends' in merged_config and 'gitea' in merged_config['backends']:
|
|
gitea_config = merged_config['backends']['gitea']
|
|
if not gitea_config.get('token'):
|
|
gitea_config['token'] = api_token
|
|
if not gitea_config.get('repo_owner'):
|
|
gitea_config['repo_owner'] = repo_owner
|
|
if not gitea_config.get('repo_name'):
|
|
gitea_config['repo_name'] = repo_name
|
|
|
|
return merged_config
|
|
except Exception:
|
|
# Return defaults if config loading fails
|
|
return default_config
|
|
|
|
def _discover_plugins(self) -> Dict[str, Type[IssueBackend]]:
|
|
"""
|
|
Discover available backend plugins.
|
|
|
|
Returns:
|
|
Dictionary mapping backend names to plugin classes
|
|
"""
|
|
plugins = {}
|
|
|
|
# Try to import known plugins
|
|
plugin_modules = [
|
|
('gitea', 'markitect.issues.plugins.gitea', 'GiteaPlugin'),
|
|
('local', 'markitect.issues.plugins.local', 'LocalPlugin'),
|
|
]
|
|
|
|
for name, module_path, class_name in plugin_modules:
|
|
try:
|
|
module = importlib.import_module(module_path)
|
|
plugin_class = getattr(module, class_name)
|
|
if issubclass(plugin_class, IssueBackend):
|
|
plugins[name] = plugin_class
|
|
except (ImportError, AttributeError):
|
|
# Plugin not available, skip
|
|
continue
|
|
|
|
return plugins |