Implement comprehensive type annotations and mypy configuration as part of code quality initiative. Achieve 100% type annotation coverage for main CLI entry points and resolve Optional type inconsistencies. ## Key Improvements ### CLI Layer (100% Type Coverage) - tddai_cli.py: Complete type annotations for all 21 functions - cli/core.py: Full type coverage for CLI framework (20 functions) - cli/commands/issues.py: Fixed Optional[List[str]] parameter types - cli/commands/workspace.py: Improved type checker logic for Optional handling ### Service Layer Type Safety - services/issue_service.py: Fixed Optional parameter type signatures - services/project_service.py: Updated Optional type annotations - tddai/issue_creator.py: Proper Optional[List[str]] usage - tddai/project_manager.py: Fixed Optional parameter handling ### Mypy Configuration - pyproject.toml: Added comprehensive mypy configuration - Gradual adoption strategy with module-specific strictness - Python 3.12 compatibility for proper type checking - Incremental typing approach for legacy modules ## Technical Details - Proper Optional vs Union type usage throughout - Generic type annotations for collections - Return type annotations for all public functions - Fixed implicit Optional violations (PEP 484) - Type checker logic improvements for better safety ## Benefits - Improved IDE autocomplete and error detection - Compile-time type checking for CLI commands - Better maintainability and debugging capabilities - Foundation for expanding type safety to remaining modules Resolves #27 - Type safety improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
6.2 KiB
Python
154 lines
6.2 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: Optional[List[str]] = None,
|
|
dependencies: Optional[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
|
|
} |