base.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. """Abstract base class for Git hosting provider backends."""
  2. import hashlib
  3. from abc import ABC, abstractmethod
  4. import httpx
  5. class GitProviderBackend(ABC):
  6. """Abstract base for Git hosting provider API backends."""
  7. @staticmethod
  8. def _blob_sha(content_bytes: bytes) -> str:
  9. """Compute the git blob SHA for content_bytes (sha1("blob {len}\\0" + data))."""
  10. return hashlib.sha1(
  11. f"blob {len(content_bytes)}\0".encode() + content_bytes, usedforsecurity=False
  12. ).hexdigest()
  13. @staticmethod
  14. def _truncated_response_text(response: httpx.Response, max_length: int = 200) -> str:
  15. """Return a bounded response body for errors surfaced to logs/UI."""
  16. text = response.text
  17. if len(text) <= max_length:
  18. return text
  19. return f"{text[: max_length - 3]}..."
  20. def get_headers(self, token: str) -> dict:
  21. """Return HTTP headers for authenticated API requests."""
  22. return {
  23. "Authorization": f"token {token}",
  24. "Accept": "application/vnd.github.v3+json",
  25. "User-Agent": "Bambuddy-Backup",
  26. }
  27. @abstractmethod
  28. def parse_repo_url(self, url: str) -> tuple[str, str]:
  29. """Return (owner, repo) extracted from the repository URL."""
  30. @abstractmethod
  31. def get_api_base(self, repo_url: str) -> str:
  32. """Return the API base URL for this provider instance."""
  33. @abstractmethod
  34. async def test_connection(self, repo_url: str, token: str, client: httpx.AsyncClient) -> dict:
  35. """Test API connectivity and push permissions. Returns success/message/repo_name/permissions."""
  36. @abstractmethod
  37. async def push_files(
  38. self,
  39. repo_url: str,
  40. token: str,
  41. branch: str,
  42. files: dict,
  43. client: httpx.AsyncClient,
  44. ) -> dict:
  45. """Push files to the repository. Returns status/message/commit_sha/files_changed."""