# Copyright 2026 The Gitea Authors. All rights reserved. # SPDX-License-Identifier: MIT """Data models for the Gitea SDK.""" from dataclasses import dataclass, field from datetime import datetime from typing import Optional, List, Dict, Any @dataclass class User: """Represents a Gitea user.""" id: int login: str full_name: str = "" email: str = "" avatar_url: str = "" is_admin: bool = False @classmethod def from_dict(cls, data: Dict[str, Any]) -> "User": return cls( id=data.get("id", 0), login=data.get("login", ""), full_name=data.get("full_name", ""), email=data.get("email", ""), avatar_url=data.get("avatar_url", ""), is_admin=data.get("is_admin", False), ) @dataclass class Repository: """Represents a Gitea repository.""" id: int name: str full_name: str owner: Optional[User] = None description: str = "" private: bool = False fork: bool = False default_branch: str = "main" stars_count: int = 0 forks_count: int = 0 clone_url: str = "" html_url: str = "" @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Repository": owner = None if data.get("owner"): owner = User.from_dict(data["owner"]) return cls( id=data.get("id", 0), name=data.get("name", ""), full_name=data.get("full_name", ""), owner=owner, description=data.get("description", ""), private=data.get("private", False), fork=data.get("fork", False), default_branch=data.get("default_branch", "main"), stars_count=data.get("stars_count", 0), forks_count=data.get("forks_count", 0), clone_url=data.get("clone_url", ""), html_url=data.get("html_url", ""), ) @dataclass class Attachment: """Represents a release attachment/asset.""" id: int name: str size: int download_count: int = 0 download_url: str = "" created_at: Optional[datetime] = None @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Attachment": created_at = None if data.get("created_at"): try: created_at = datetime.fromisoformat(data["created_at"].replace("Z", "+00:00")) except (ValueError, AttributeError): pass return cls( id=data.get("id", 0), name=data.get("name", ""), size=data.get("size", 0), download_count=data.get("download_count", 0), download_url=data.get("browser_download_url", ""), created_at=created_at, ) @dataclass class Release: """Represents a Gitea release.""" id: int tag_name: str name: str = "" body: str = "" draft: bool = False prerelease: bool = False published_at: Optional[datetime] = None assets: List[Attachment] = field(default_factory=list) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Release": published_at = None if data.get("published_at"): try: published_at = datetime.fromisoformat(data["published_at"].replace("Z", "+00:00")) except (ValueError, AttributeError): pass assets = [] for asset_data in data.get("assets", []): assets.append(Attachment.from_dict(asset_data)) return cls( id=data.get("id", 0), tag_name=data.get("tag_name", ""), name=data.get("name", ""), body=data.get("body", ""), draft=data.get("draft", False), prerelease=data.get("prerelease", False), published_at=published_at, assets=assets, ) @dataclass class UploadSession: """Represents a chunked upload session.""" id: str file_name: str file_size: int chunk_size: int total_chunks: int chunks_received: int = 0 status: str = "pending" expires_at: Optional[datetime] = None checksum: str = "" @classmethod def from_dict(cls, data: Dict[str, Any]) -> "UploadSession": expires_at = None if data.get("expires_at"): try: expires_at = datetime.fromisoformat(data["expires_at"].replace("Z", "+00:00")) except (ValueError, AttributeError): pass return cls( id=data.get("id", ""), file_name=data.get("file_name", ""), file_size=data.get("file_size", 0), chunk_size=data.get("chunk_size", 0), total_chunks=data.get("total_chunks", 0), chunks_received=data.get("chunks_received", 0), status=data.get("status", "pending"), expires_at=expires_at, checksum=data.get("checksum", ""), ) @dataclass class UploadResult: """Represents the result of a completed upload.""" id: int name: str size: int download_url: str checksum_verified: bool = False @classmethod def from_dict(cls, data: Dict[str, Any]) -> "UploadResult": return cls( id=data.get("id", 0), name=data.get("name", ""), size=data.get("size", 0), download_url=data.get("browser_download_url", ""), checksum_verified=data.get("checksum_verified", False), ) @dataclass class Progress: """Represents upload progress.""" bytes_done: int bytes_total: int chunks_done: int chunks_total: int percent: float speed: float # bytes per second eta_seconds: float @property def eta(self) -> str: """Format ETA as a human-readable string.""" seconds = int(self.eta_seconds) if seconds < 60: return f"{seconds}s" minutes = seconds // 60 seconds = seconds % 60 if minutes < 60: return f"{minutes}m{seconds}s" hours = minutes // 60 minutes = minutes % 60 return f"{hours}h{minutes}m" @property def speed_formatted(self) -> str: """Format speed as a human-readable string.""" if self.speed < 1024: return f"{self.speed:.0f} B/s" elif self.speed < 1024 * 1024: return f"{self.speed / 1024:.1f} KB/s" else: return f"{self.speed / 1024 / 1024:.1f} MB/s"