Files
markitect-main/gitea/models.py
tegwick fd8f792f08 refactor: Factor out Gitea interfacing into clean facade pattern
- 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>
2025-09-26 14:25:40 +02:00

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 = ""