membra-agent-workforce / app /services /github_service.py
josephrw's picture
Upload app/services/github_service.py with huggingface_hub
7b89ea4 verified
Raw
History Blame Contribute Delete
4.88 kB
import base64
import httpx
from typing import Optional
from app.core.config import settings
from app.core.logging import get_logger
from app.models.schemas import GitHubRepoRequest, GitHubRepoResponse
logger = get_logger("github_service")
class GitHubService:
BASE = "https://api.github.com"
def __init__(self):
self.token = settings.github_token
self.org = settings.github_org
self.client = httpx.AsyncClient(timeout=60.0)
def _headers(self):
return {
"Authorization": f"token {self.token}",
"Accept": "application/vnd.github.v3+json",
}
async def create_repo(self, req: GitHubRepoRequest) -> GitHubRepoResponse:
if not self.token:
return GitHubRepoResponse(repo_url="", clone_url="", created=False)
try:
path = f"/orgs/{self.org}/repos" if self.org else "/user/repos"
payload = {
"name": req.name,
"description": req.description or f"Auto-created repo for {req.name}",
"private": req.private,
"auto_init": True,
}
resp = await self.client.post(f"{self.BASE}{path}", headers=self._headers(), json=payload)
resp.raise_for_status()
data = resp.json()
repo_url = data["html_url"]
clone_url = data["clone_url"]
if req.readme_content:
await self._update_readme(req.name, req.readme_content)
logger.info("repo_created", name=req.name, url=repo_url)
return GitHubRepoResponse(repo_url=repo_url, clone_url=clone_url, created=True)
except Exception as e:
logger.error("repo_creation_failed", name=req.name, error=str(e))
return GitHubRepoResponse(repo_url="", clone_url="", created=False)
async def _update_readme(self, repo_name: str, content: str):
try:
path = f"{self.BASE}/repos/{self.org or 'user'}/{repo_name}/contents/README.md"
if self.org:
path = f"{self.BASE}/repos/{self.org}/{repo_name}/contents/README.md"
payload = {
"message": "docs: update README via AgentWorkforce",
"content": base64.b64encode(content.encode()).decode(),
}
resp = await self.client.put(path, headers=self._headers(), json=payload)
resp.raise_for_status()
except Exception as e:
logger.error("readme_update_failed", repo=repo_name, error=str(e))
async def create_file(self, repo_name: str, path: str, content: str, message: str = "Auto commit via AgentWorkforce"):
if not self.token:
return {"status": "skipped"}
try:
full_repo = f"{self.org}/{repo_name}" if self.org else repo_name
url = f"{self.BASE}/repos/{full_repo}/contents/{path}"
payload = {
"message": message,
"content": base64.b64encode(content.encode()).decode(),
}
resp = await self.client.put(url, headers=self._headers(), json=payload)
resp.raise_for_status()
return {"status": "created", "path": path}
except Exception as e:
# Try update if exists
logger.warning("file_create_failed_trying_update", path=path, error=str(e))
return await self.update_file(repo_name, path, content, message)
async def update_file(self, repo_name: str, path: str, content: str, message: str):
try:
full_repo = f"{self.org}/{repo_name}" if self.org else repo_name
url = f"{self.BASE}/repos/{full_repo}/contents/{path}"
# Get SHA first
r = await self.client.get(url, headers=self._headers())
if r.status_code != 200:
return {"status": "error", "detail": r.text}
sha = r.json()["sha"]
payload = {
"message": message,
"content": base64.b64encode(content.encode()).decode(),
"sha": sha,
}
resp = await self.client.put(url, headers=self._headers(), json=payload)
resp.raise_for_status()
return {"status": "updated", "path": path}
except Exception as e:
logger.error("file_update_failed", path=path, error=str(e))
return {"status": "error", "error": str(e)}
async def create_repo_and_push(self, repo_name: str, files: dict, private: bool = True, description: str = "") -> GitHubRepoResponse:
repo = await self.create_repo(GitHubRepoRequest(name=repo_name, private=private, description=description))
if not repo.created:
return repo
for path, content in files.items():
await self.create_file(repo_name, path, content)
return repo
github_service = GitHubService()