Files
markitect-main/tddai/issue_creator.py
tegwick a3093e1443 feat: Complete type safety improvements for CLI and service layers
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>
2025-09-27 09:02:31 +02:00

211 lines
6.9 KiB
Python

"""
Issue creation using the Gitea facade.
This module now acts as an adapter to the new gitea package,
maintaining backwards compatibility while using the cleaner API.
"""
import os
from typing import Dict, Any, Optional, List
from gitea import GiteaClient, GiteaConfig, Priority
from .config import get_config
from .exceptions import IssueError
class IssueCreator:
"""Creates new issues using the Gitea facade."""
def __init__(self, config=None, auth_token=None):
self.config = config or get_config()
self.auth_token = auth_token or os.getenv('GITEA_API_TOKEN')
# Create Gitea client from tddai config
gitea_config = GiteaConfig.from_tddai_config(self.config)
if self.auth_token:
gitea_config.auth_token = self.auth_token
self.gitea_client = GiteaClient(gitea_config)
def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]:
"""Create a new issue via POST operation.
Args:
title: Issue title (required)
body: Issue description/body (required)
**kwargs: Optional fields (assignees, milestone, labels, etc.)
Returns:
Dict containing created issue data including issue number
Raises:
IssueError: If creation fails
"""
# Validate input
if not title or not title.strip():
raise IssueError("Issue title cannot be empty")
try:
issue = self.gitea_client.issues.create(
title=title,
body=body,
assignees=kwargs.get('assignees', []),
milestone=kwargs.get('milestone'),
labels=kwargs.get('labels', [])
)
# Convert back to dict format for backwards compatibility
return {
'number': issue.number,
'title': issue.title,
'body': issue.body,
'state': issue.state,
'html_url': issue.html_url,
'created_at': issue.created_at.isoformat(),
'updated_at': issue.updated_at.isoformat(),
'assignee': {'login': issue.assignee.login} if issue.assignee else None,
'labels': [{'name': label.name} for label in issue.labels]
}
except Exception as e:
raise IssueError(f"Failed to create issue: {e}")
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 an enhancement issue with structured format.
Args:
title: Issue title
use_case: UseCase description
technical_requirements: Technical implementation details
acceptance_criteria: List of acceptance criteria
dependencies: List of dependency descriptions
priority: Priority level (High, Medium, Low)
Returns:
Dict containing created issue data
"""
# Build structured body
body_parts = [f"UseCase: {use_case}"]
if technical_requirements:
body_parts.extend([
"",
"Technical Requirements:",
technical_requirements
])
if acceptance_criteria:
body_parts.extend([
"",
"Acceptance Criteria:"
])
for criterion in acceptance_criteria:
body_parts.append(f"- [ ] {criterion}")
if dependencies:
body_parts.extend([
"",
"Dependencies:"
])
for dep in dependencies:
body_parts.append(f"- {dep}")
body = "\n".join(body_parts)
# Create with enhancement label
return self.create_issue(
title=title,
body=body,
labels=[priority.lower(), "enhancement"]
)
def create_bug_issue(self, title: str, description: str,
steps_to_reproduce: Optional[List[str]] = None,
expected_behavior: str = "",
actual_behavior: str = "",
environment: str = "") -> Dict[str, Any]:
"""Create a bug issue with structured format.
Args:
title: Bug title
description: Bug description
steps_to_reproduce: List of reproduction steps
expected_behavior: What should happen
actual_behavior: What actually happens
environment: Environment details
Returns:
Dict containing created issue data
"""
body_parts = [description]
if steps_to_reproduce:
body_parts.extend([
"",
"Steps to Reproduce:"
])
for i, step in enumerate(steps_to_reproduce, 1):
body_parts.append(f"{i}. {step}")
if expected_behavior:
body_parts.extend([
"",
f"Expected Behavior: {expected_behavior}"
])
if actual_behavior:
body_parts.extend([
"",
f"Actual Behavior: {actual_behavior}"
])
if environment:
body_parts.extend([
"",
f"Environment: {environment}"
])
body = "\n".join(body_parts)
# Create with bug label
return self.create_issue(
title=title,
body=body,
labels=["bug"]
)
def create_from_template(self, template_file: str, **template_vars) -> Dict[str, Any]:
"""Create issue from a template file.
Args:
template_file: Path to template file
**template_vars: Variables to substitute in template
Returns:
Dict containing created issue data
"""
try:
with open(template_file, 'r') as f:
template_content = f.read()
# Simple template variable substitution
for key, value in template_vars.items():
template_content = template_content.replace(f"{{{key}}}", str(value))
# Extract title (first line) and body (rest)
lines = template_content.strip().split('\n')
if not lines or (len(lines) == 1 and not lines[0].strip()):
raise IssueError("Template file is empty")
title = lines[0].replace('Title: ', '').strip()
body = '\n'.join(lines[1:]).strip()
return self.create_issue(title=title, body=body)
except FileNotFoundError:
raise IssueError(f"Template file not found: {template_file}")
except Exception as e:
raise IssueError(f"Failed to process template: {e}")