""" Main Gitea client facade. This provides a clean, organized interface for all Gitea operations, following the facade pattern to hide complexity and provide a stable API. """ from typing import List, Optional, Dict, Any from .config import GiteaConfig from .api_client import GiteaApiClient from .models import Issue, Milestone, Label, IssueCreateData, IssueUpdateData, MilestoneCreateData, LabelCreateData, ProjectState, Priority class IssuesClient: """Client for issue operations.""" def __init__(self, api_client: GiteaApiClient): self._api = api_client def get(self, issue_number: int) -> Issue: """Get a specific issue by number.""" return self._api.get_issue(issue_number) def list(self, state: str = "all", page: int = 1, per_page: int = 50) -> List[Issue]: """List issues with optional filtering.""" return self._api.list_issues(state, page, per_page) def list_open(self) -> List[Issue]: """List only open issues.""" return self._api.list_issues("open") def list_closed(self) -> List[Issue]: """List only closed issues.""" return self._api.list_issues("closed") def create(self, title: str, body: str = "", **kwargs) -> Issue: """Create a new issue.""" issue_data = IssueCreateData( title=title, body=body, assignees=kwargs.get('assignees', []), milestone=kwargs.get('milestone'), labels=kwargs.get('labels', []) ) return self._api.create_issue(issue_data) def update(self, issue_number: int, **kwargs) -> Issue: """Update an existing issue.""" update_data = IssueUpdateData( title=kwargs.get('title'), body=kwargs.get('body'), state=kwargs.get('state'), assignees=kwargs.get('assignees'), milestone=kwargs.get('milestone'), labels=kwargs.get('labels') ) return self._api.update_issue(issue_number, update_data) def close(self, issue_number: int) -> Issue: """Close an issue.""" return self.update(issue_number, state="closed") def reopen(self, issue_number: int) -> Issue: """Reopen an issue.""" return self.update(issue_number, state="open") def add_labels(self, issue_number: int, labels: List[str]) -> Issue: """Add labels to an issue.""" issue = self.get(issue_number) existing_labels = [label.name for label in issue.labels] new_labels = list(set(existing_labels + labels)) return self.update(issue_number, labels=new_labels) def remove_labels(self, issue_number: int, labels: List[str]) -> Issue: """Remove labels from an issue.""" issue = self.get(issue_number) existing_labels = [label.name for label in issue.labels] new_labels = [label for label in existing_labels if label not in labels] return self.update(issue_number, labels=new_labels) def set_priority(self, issue_number: int, priority: Priority) -> Issue: """Set issue priority.""" issue = self.get(issue_number) labels = [label.name for label in issue.labels if not label.name.startswith('priority:')] labels.append(priority.value) return self.update(issue_number, labels=labels) def set_status(self, issue_number: int, status: ProjectState) -> Issue: """Set issue status.""" issue = self.get(issue_number) labels = [label.name for label in issue.labels if not label.name.startswith('status:')] labels.append(status.value) return self.update(issue_number, labels=labels) def assign_to_milestone(self, issue_number: int, milestone_id: int) -> Issue: """Assign issue to a milestone.""" return self.update(issue_number, milestone=milestone_id) def remove_from_milestone(self, issue_number: int) -> Issue: """Remove issue from milestone.""" return self.update(issue_number, milestone=None) def set_labels(self, issue_number: int, labels: List[str]) -> Issue: """Replace all labels on an issue.""" return self.update(issue_number, labels=labels) def update_title(self, issue_number: int, title: str) -> Issue: """Update only the title of an issue.""" return self.update(issue_number, title=title) def update_body(self, issue_number: int, body: str) -> Issue: """Update only the body of an issue.""" return self.update(issue_number, body=body) def to_dict(self, issue: Issue) -> Dict[str, Any]: """Convert Issue object to dictionary format for backward 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, 'color': label.color} for label in issue.labels], 'milestone': { 'id': issue.milestone.id, 'title': issue.milestone.title } if issue.milestone else None } class MilestonesClient: """Client for milestone operations.""" def __init__(self, api_client: GiteaApiClient): self._api = api_client def list(self, state: str = "all") -> List[Milestone]: """List milestones.""" return self._api.list_milestones(state) def list_open(self) -> List[Milestone]: """List open milestones.""" return self._api.list_milestones("open") def list_closed(self) -> List[Milestone]: """List closed milestones.""" return self._api.list_milestones("closed") def create(self, title: str, description: str = "", due_on: str = None) -> Milestone: """Create a new milestone.""" milestone_data = MilestoneCreateData( title=title, description=description, due_on=due_on ) return self._api.create_milestone(milestone_data) class LabelsClient: """Client for label operations.""" def __init__(self, api_client: GiteaApiClient): self._api = api_client def list(self) -> List[Label]: """List all labels.""" return self._api.list_labels() def create(self, name: str, color: str, description: str = "") -> Label: """Create a new label.""" label_data = LabelCreateData( name=name, color=color, description=description ) return self._api.create_label(label_data) def ensure_project_labels(self) -> None: """Ensure all standard project management labels exist.""" existing_labels = [label.name for label in self.list()] # Define standard project labels standard_labels = [ ("status:todo", "d73a4a", "Ready to work on"), ("status:active", "0075ca", "Currently being worked on"), ("status:review", "fbca04", "Ready for review"), ("status:done", "0e8a16", "Completed work"), ("status:blocked", "b60205", "Blocked by dependencies"), ("priority:low", "c5def5", "Low priority"), ("priority:medium", "a2eeef", "Medium priority"), ("priority:high", "fef2c0", "High priority"), ("priority:critical", "d93f0b", "Critical priority"), ] for name, color, description in standard_labels: if name not in existing_labels: self.create(name, color, description) class GiteaClient: """Main Gitea client facade.""" def __init__(self, config: Optional[GiteaConfig] = None): """Initialize Gitea client. Args: config: GiteaConfig instance. If None, auto-detects from git repository. """ if config is None: try: config = GiteaConfig.from_git_repository() except Exception: # Fallback to environment-based config if git detection fails config = GiteaConfig.from_environment() config.validate() self.config = config self._api = GiteaApiClient(config) # Initialize sub-clients self.issues = IssuesClient(self._api) self.milestones = MilestonesClient(self._api) self.labels = LabelsClient(self._api) @classmethod def from_tddai_config(cls, tddai_config) -> 'GiteaClient': """Create client from legacy TddaiConfig for backwards compatibility.""" gitea_config = GiteaConfig.from_tddai_config(tddai_config) return cls(gitea_config) def setup_project_management(self) -> None: """Setup standard project management labels and structure.""" self.labels.ensure_project_labels()