gogs_client.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import httpx
  2. from app.config import settings
  3. import logging
  4. logger = logging.getLogger(__name__)
  5. class GogsClient:
  6. def __init__(self):
  7. self.base_url = settings.GOGS_URL.rstrip('/')
  8. self.token = settings.GOGS_TOKEN
  9. self.headers = {"Authorization": f"token {self.token}"}
  10. async def get_manifest(self, owner: str, repo: str, commit_id: str) -> str:
  11. """Fetch manifest.yaml raw content from a specific commit."""
  12. # Gogs raw file URL format: /{owner}/{repo}/raw/{ref}/{path}
  13. url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/raw/{commit_id}/manifest.yaml"
  14. async with httpx.AsyncClient() as client:
  15. resp = await client.get(url, headers=self.headers)
  16. if resp.status_code == 404:
  17. return None
  18. resp.raise_for_status()
  19. return resp.text
  20. async def get_tree(self, owner: str, repo: str, commit_id: str, path: str = "") -> list:
  21. """Get the file tree of a repository."""
  22. url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/contents/{path}?ref={commit_id}"
  23. async with httpx.AsyncClient() as client:
  24. resp = await client.get(url, headers=self.headers)
  25. resp.raise_for_status()
  26. return resp.json()
  27. async def get_file_info(self, owner: str, repo: str, commit_id: str, file_path: str) -> dict | None:
  28. """Get single file info including SHA.
  29. Returns dict with 'sha', 'size', 'path' or None if not found.
  30. """
  31. url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/contents/{file_path}?ref={commit_id}"
  32. try:
  33. async with httpx.AsyncClient() as client:
  34. resp = await client.get(url, headers=self.headers)
  35. if resp.status_code == 404:
  36. return None
  37. resp.raise_for_status()
  38. data = resp.json()
  39. # contents API returns file info directly for single file
  40. if isinstance(data, dict) and data.get("type") == "file":
  41. return {
  42. "path": file_path,
  43. "sha": data.get("sha"),
  44. "size": data.get("size", 0),
  45. "type": "blob"
  46. }
  47. return None
  48. except httpx.HTTPStatusError as e:
  49. logger.error(f"Failed to get file info for {file_path}: {e}")
  50. return None
  51. async def get_directory_tree(self, owner: str, repo: str, commit_id: str, dir_path: str) -> list:
  52. """Get all files under a specific directory (recursive).
  53. Args:
  54. dir_path: Directory path without trailing slash (e.g., "data/output")
  55. Returns:
  56. List of file info dicts with 'path', 'sha', 'size', 'type'
  57. """
  58. all_files = []
  59. async def fetch_contents(path: str):
  60. """Recursively fetch directory contents using contents API."""
  61. url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/contents/{path}?ref={commit_id}"
  62. try:
  63. async with httpx.AsyncClient() as client:
  64. resp = await client.get(url, headers=self.headers)
  65. if resp.status_code == 404:
  66. logger.warning(f"Directory not found: {path}")
  67. return
  68. resp.raise_for_status()
  69. data = resp.json()
  70. # contents API returns list for directories
  71. if isinstance(data, list):
  72. for item in data:
  73. if item.get("type") == "file":
  74. all_files.append({
  75. "path": item.get("path"),
  76. "sha": item.get("sha"),
  77. "size": item.get("size", 0),
  78. "type": "blob"
  79. })
  80. elif item.get("type") == "dir":
  81. # Recursively fetch subdirectory
  82. await fetch_contents(item.get("path"))
  83. except httpx.HTTPStatusError as e:
  84. logger.error(f"Failed to get contents for {path}: {e}")
  85. await fetch_contents(dir_path)
  86. return all_files
  87. async def get_file_content(self, owner: str, repo: str, commit_id: str, file_path: str) -> bytes:
  88. """Download raw file content."""
  89. # Gogs raw file URL format: /{owner}/{repo}/raw/{ref}/{path}
  90. url = f"{self.base_url}/api/v1/repos/{owner}/{repo}/raw/{commit_id}/{file_path}"
  91. async with httpx.AsyncClient() as client:
  92. resp = await client.get(url, headers=self.headers)
  93. resp.raise_for_status()
  94. return resp.content