- Create new gitea/ package with clean API facade - Establish proper separation of concerns: tddai uses gitea, not vice versa - Replace duplicate curl+subprocess patterns with unified HTTP client - Add rich domain models with properties (issue.priority, issue.status) - Maintain full backwards compatibility in tddai modules - Reduce code complexity: -373 lines, +151 lines (net -222 lines) - Improve testability and maintainability through clean interfaces Architecture: - gitea.client.GiteaClient - main facade with sub-clients - gitea.api_client - high-level API with model conversion - gitea.http_client - low-level HTTP operations - gitea.models - rich domain objects (Issue, Milestone, Label) - gitea.config - gitea-specific configuration - gitea.exceptions - clean exception hierarchy 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
151 lines
3.6 KiB
Python
151 lines
3.6 KiB
Python
"""
|
|
Gitea domain models.
|
|
|
|
These models represent the core entities in Gitea and provide a clean interface
|
|
independent of the underlying API representation.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
|
|
class ProjectState(Enum):
|
|
"""Standard project states using labels."""
|
|
TODO = "status:todo"
|
|
ACTIVE = "status:active"
|
|
REVIEW = "status:review"
|
|
DONE = "status:done"
|
|
BLOCKED = "status:blocked"
|
|
|
|
|
|
class Priority(Enum):
|
|
"""Priority levels using labels."""
|
|
LOW = "priority:low"
|
|
MEDIUM = "priority:medium"
|
|
HIGH = "priority:high"
|
|
CRITICAL = "priority:critical"
|
|
|
|
|
|
@dataclass
|
|
class Label:
|
|
"""Represents a Gitea issue label."""
|
|
id: int
|
|
name: str
|
|
color: str
|
|
description: str = ""
|
|
|
|
|
|
@dataclass
|
|
class User:
|
|
"""Represents a Gitea user."""
|
|
id: int
|
|
login: str
|
|
full_name: str = ""
|
|
email: str = ""
|
|
avatar_url: str = ""
|
|
|
|
|
|
@dataclass
|
|
class Milestone:
|
|
"""Represents a Gitea milestone (used as projects)."""
|
|
id: int
|
|
title: str
|
|
description: str
|
|
state: str # 'open' or 'closed'
|
|
open_issues: int
|
|
closed_issues: int
|
|
due_on: Optional[str] = None
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
|
|
@dataclass
|
|
class Issue:
|
|
"""Represents a Gitea issue."""
|
|
number: int
|
|
title: str
|
|
body: str
|
|
state: str # 'open' or 'closed'
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
html_url: str
|
|
assignee: Optional[User] = None
|
|
labels: List[Label] = None
|
|
milestone: Optional[Milestone] = None
|
|
|
|
def __post_init__(self):
|
|
if self.labels is None:
|
|
self.labels = []
|
|
|
|
@property
|
|
def priority(self) -> Optional[str]:
|
|
"""Get issue priority from labels."""
|
|
for label in self.labels:
|
|
if label.name.startswith('priority:'):
|
|
return label.name.replace('priority:', '')
|
|
return None
|
|
|
|
@property
|
|
def status(self) -> Optional[str]:
|
|
"""Get issue status from labels."""
|
|
for label in self.labels:
|
|
if label.name.startswith('status:'):
|
|
return label.name.replace('status:', '')
|
|
return None
|
|
|
|
def has_label(self, label_name: str) -> bool:
|
|
"""Check if issue has a specific label."""
|
|
return any(label.name == label_name for label in self.labels)
|
|
|
|
def has_priority(self, priority: Priority) -> bool:
|
|
"""Check if issue has a specific priority."""
|
|
return self.has_label(priority.value)
|
|
|
|
def has_status(self, status: ProjectState) -> bool:
|
|
"""Check if issue has a specific status."""
|
|
return self.has_label(status.value)
|
|
|
|
|
|
@dataclass
|
|
class IssueCreateData:
|
|
"""Data for creating a new issue."""
|
|
title: str
|
|
body: str = ""
|
|
assignees: List[str] = None
|
|
milestone: Optional[int] = None
|
|
labels: List[str] = None
|
|
|
|
def __post_init__(self):
|
|
if self.assignees is None:
|
|
self.assignees = []
|
|
if self.labels is None:
|
|
self.labels = []
|
|
|
|
|
|
@dataclass
|
|
class IssueUpdateData:
|
|
"""Data for updating an existing issue."""
|
|
title: Optional[str] = None
|
|
body: Optional[str] = None
|
|
state: Optional[str] = None
|
|
assignees: Optional[List[str]] = None
|
|
milestone: Optional[int] = None
|
|
labels: Optional[List[str]] = None
|
|
|
|
|
|
@dataclass
|
|
class MilestoneCreateData:
|
|
"""Data for creating a new milestone."""
|
|
title: str
|
|
description: str = ""
|
|
due_on: Optional[str] = None
|
|
|
|
|
|
@dataclass
|
|
class LabelCreateData:
|
|
"""Data for creating a new label."""
|
|
name: str
|
|
color: str
|
|
description: str = "" |