Files
markitect-main/services/issue_service.py
tegwick bbc6192fe1 refactor: Standardize error handling patterns across codebase
Comprehensive error handling improvements addressing inconsistent patterns:

• Created markitect/exceptions.py with complete domain-specific exception hierarchy
  - MarkitectError base class with context and cause chaining support
  - Specific exceptions for Document, AST, Cache, Database, Schema operations
  - Built-in logging and context preservation

• Fixed overly broad exception handling in tddai modules:
  - issue_fetcher.py: Replace generic Exception with specific Gitea errors
  - project_manager.py: Proper error translation with context preservation
  - coverage_analyzer.py: Replace silent suppression with logging

• Enhanced cache_service.py error handling:
  - Specific OSError/PermissionError handling for file operations
  - Logging integration for unexpected errors
  - Preserved error collection and reporting

• Implemented proper exception chaining patterns:
  - All error translations use `raise ... from e` for debugging
  - Preserved original exception context and stack traces
  - Added docstring declarations of raised exceptions

• Benefits:
  - Eliminates silent error suppression and debugging black holes
  - Provides specific, actionable error messages
  - Preserves full error context for troubleshooting
  - Establishes consistent patterns for future development

Resolves issue #21: Error handling standardization

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 16:35:13 +02:00

154 lines
6.1 KiB
Python

"""
Issue service - business logic for issue operations.
"""
from typing import List, Dict, Any, Optional
from datetime import datetime
from tddai import IssueFetcher, TddaiError
from tddai.issue_creator import IssueCreator
from gitea.models import Issue, Priority
class IssueService:
"""Service for issue operations."""
def __init__(self):
self.issue_fetcher = IssueFetcher()
self.issue_creator = IssueCreator()
def get_issue(self, issue_number: int) -> Issue:
"""Get a specific issue by number."""
return self.issue_fetcher.fetch_issue(issue_number)
def list_issues(self, state: str = "all") -> List[Issue]:
"""List issues with optional state filter."""
return self.issue_fetcher.fetch_issues(state)
def list_open_issues(self) -> List[Issue]:
"""List only open issues."""
return self.issue_fetcher.fetch_open_issues()
def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]:
"""Create a new issue."""
return self.issue_creator.create_issue(title, body, **kwargs)
def create_enhancement_issue(self, title: str, use_case: str,
technical_requirements: str = "",
acceptance_criteria: List[str] = None,
dependencies: List[str] = None,
priority: str = "Medium") -> Dict[str, Any]:
"""Create a structured enhancement issue."""
return self.issue_creator.create_enhancement_issue(
title, use_case, technical_requirements,
acceptance_criteria, dependencies, priority
)
def create_from_template(self, template_file: str, **kwargs) -> Dict[str, Any]:
"""Create issue from template file."""
return self.issue_creator.create_from_template(template_file, **kwargs)
def get_issue_details(self, issue_number: int) -> Dict[str, Any]:
"""Get comprehensive issue details for display purposes."""
issue = self.get_issue(issue_number)
# Get additional project management information
from tddai.project_manager import ProjectManager
project_mgr = ProjectManager()
# Get detailed issue data via API for milestone and project information
from tddai.config import get_config
config = get_config()
issue_url = f"{config.issues_api_url}/{issue_number}"
detailed_issue = project_mgr._make_api_call('GET', issue_url)
# Process labels
labels = detailed_issue.get('labels', [])
state_labels = [l['name'] for l in labels if l['name'].startswith('status:')]
priority_labels = [l['name'] for l in labels if l['name'].startswith('priority:')]
type_labels = [l['name'] for l in labels if l['name'].startswith('type:')]
other_labels = [l['name'] for l in labels if not any(l['name'].startswith(p) for p in ['status:', 'priority:', 'type:'])]
# Determine project column/state
if detailed_issue.get('state') == 'closed':
if any(l['name'] == 'status:done' for l in labels):
column = "Done"
else:
column = "Closed"
else:
state_labels = [l['name'] for l in labels if l['name'].startswith('status:')]
if state_labels:
state = state_labels[0].replace('status:', '')
column_map = {
'todo': 'Todo',
'active': 'Active',
'review': 'Review',
'blocked': 'Blocked'
}
column = column_map.get(state, 'Todo')
else:
column = "Todo"
return {
'number': issue.number,
'title': issue.title,
'body': issue.body,
'state': issue.state,
'created_at': issue.created_at,
'updated_at': issue.updated_at,
'html_url': issue.html_url,
'assignee': issue.assignee.login if issue.assignee else None,
'milestone': detailed_issue.get('milestone'),
'state_label': state_labels[0].replace('status:', '').title() if state_labels else "No state label",
'priority_label': priority_labels[0].replace('priority:', '').title() if priority_labels else "No priority set",
'type_labels': [l.replace('type:', '').title() for l in type_labels],
'other_labels': other_labels,
'kanban_column': column
}
def get_issue_summary(self, issue_number: int) -> Dict[str, Any]:
"""Get issue summary for display purposes."""
issue = self.get_issue(issue_number)
return {
'number': issue.number,
'title': issue.title,
'body': issue.body,
'state': issue.state,
'priority': issue.priority,
'status': issue.status,
'created_at': issue.created_at,
'updated_at': issue.updated_at,
'html_url': issue.html_url,
'assignee': issue.assignee.login if issue.assignee else None,
'labels': [label.name for label in issue.labels],
'has_milestone': issue.milestone is not None,
'milestone_title': issue.milestone.title if issue.milestone else None
}
def analyze_coverage(self, issue_number: int) -> Dict[str, Any]:
"""Analyze test coverage for a specific issue.
Args:
issue_number: The issue number to analyze
Returns:
Coverage analysis data for the issue
Raises:
IssueError: When coverage analysis fails
"""
from tddai.coverage_analyzer import CoverageAnalyzer
analyzer = CoverageAnalyzer()
assessment = analyzer.analyze_issue_coverage(issue_number)
return {
'issue_number': assessment.issue_number,
'issue_title': assessment.issue_title,
'coverage_percentage': assessment.coverage_percentage,
'requirements': assessment.requirements,
'existing_tests': assessment.existing_tests,
'coverage_gaps': assessment.coverage_gaps,
'recommendations': assessment.recommendations
}