feat(integrations): add base classes and exceptions
This commit is contained in:
33
src/arbiter/integrations/__init__.py
Normal file
33
src/arbiter/integrations/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""Platform integration clients for GitHub and GitLab."""
|
||||||
|
|
||||||
|
from arbiter.integrations.base import (
|
||||||
|
CommitStatus,
|
||||||
|
Platform,
|
||||||
|
PlatformClient,
|
||||||
|
PullRequestInfo,
|
||||||
|
)
|
||||||
|
from arbiter.integrations.exceptions import (
|
||||||
|
AuthenticationError,
|
||||||
|
IntegrationError,
|
||||||
|
NotFoundError,
|
||||||
|
PlatformError,
|
||||||
|
RateLimitError,
|
||||||
|
)
|
||||||
|
from arbiter.integrations.formatters import ReviewCommentFormatter
|
||||||
|
from arbiter.integrations.github import GitHubClient
|
||||||
|
from arbiter.integrations.gitlab import GitLabClient
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AuthenticationError",
|
||||||
|
"CommitStatus",
|
||||||
|
"GitHubClient",
|
||||||
|
"GitLabClient",
|
||||||
|
"IntegrationError",
|
||||||
|
"NotFoundError",
|
||||||
|
"Platform",
|
||||||
|
"PlatformClient",
|
||||||
|
"PlatformError",
|
||||||
|
"PullRequestInfo",
|
||||||
|
"RateLimitError",
|
||||||
|
"ReviewCommentFormatter",
|
||||||
|
]
|
||||||
92
src/arbiter/integrations/base.py
Normal file
92
src/arbiter/integrations/base.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
"""Base classes and types for platform integrations."""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(StrEnum):
|
||||||
|
"""Supported code hosting platforms."""
|
||||||
|
|
||||||
|
GITHUB = "github"
|
||||||
|
GITLAB = "gitlab"
|
||||||
|
|
||||||
|
|
||||||
|
class CommitStatus(StrEnum):
|
||||||
|
"""Commit status states for PR checks."""
|
||||||
|
|
||||||
|
PENDING = "pending"
|
||||||
|
SUCCESS = "success"
|
||||||
|
FAILURE = "failure"
|
||||||
|
ERROR = "error"
|
||||||
|
|
||||||
|
|
||||||
|
class PullRequestInfo(BaseModel):
|
||||||
|
"""Information about a pull request or merge request."""
|
||||||
|
|
||||||
|
platform: Platform = Field(description="Source platform")
|
||||||
|
repository: str = Field(description="Repository name (owner/repo format)")
|
||||||
|
pr_number: int = Field(description="PR/MR number")
|
||||||
|
head_sha: str = Field(description="Head commit SHA")
|
||||||
|
base_sha: str = Field(description="Base commit SHA")
|
||||||
|
head_ref: str = Field(description="Head branch name")
|
||||||
|
base_ref: str = Field(description="Base branch name")
|
||||||
|
title: str = Field(description="PR/MR title")
|
||||||
|
author: str | None = Field(default=None, description="PR/MR author username")
|
||||||
|
url: str = Field(description="Web URL to the PR/MR")
|
||||||
|
is_draft: bool = Field(default=False, description="Whether PR/MR is a draft")
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(BaseModel):
|
||||||
|
"""A comment on a pull request or merge request."""
|
||||||
|
|
||||||
|
id: str = Field(description="Comment ID on the platform")
|
||||||
|
body: str = Field(description="Comment body text")
|
||||||
|
author: str = Field(description="Comment author username")
|
||||||
|
url: str = Field(description="Web URL to the comment")
|
||||||
|
created_at: datetime = Field(description="When the comment was created")
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformClient(ABC):
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def platform(self) -> Platform: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_pr_diff(self, repository: str, pr_number: int) -> str: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def post_comment(self, repository: str, pr_number: int, body: str) -> str: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def update_commit_status(
|
||||||
|
self,
|
||||||
|
repository: str,
|
||||||
|
sha: str,
|
||||||
|
status: CommitStatus,
|
||||||
|
description: str,
|
||||||
|
context: str,
|
||||||
|
target_url: str | None = None,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_pr_info(self, repository: str, pr_number: int) -> PullRequestInfo: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_comments(self, repository: str, pr_number: int) -> list[Comment]: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def update_comment(
|
||||||
|
self, repository: str, pr_number: int, comment_id: str, body: str
|
||||||
|
) -> str: ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def close(self) -> None: ...
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "PlatformClient":
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args: object) -> None:
|
||||||
|
await self.close()
|
||||||
47
src/arbiter/integrations/exceptions.py
Normal file
47
src/arbiter/integrations/exceptions.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""Exception types for platform integrations."""
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationError(Exception):
|
||||||
|
"""Base exception for all platform integration errors."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
status_code: int | None = None,
|
||||||
|
response_body: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.status_code = status_code
|
||||||
|
self.response_body = response_body
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationError(IntegrationError):
|
||||||
|
"""Raised on 401 Unauthorized or 403 Forbidden responses."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimitError(IntegrationError):
|
||||||
|
"""Raised on 429 Too Many Requests responses."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
retry_after: int | None = None,
|
||||||
|
status_code: int | None = 429,
|
||||||
|
response_body: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message, status_code, response_body)
|
||||||
|
self.retry_after = retry_after
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundError(IntegrationError):
|
||||||
|
"""Raised on 404 Not Found responses."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformError(IntegrationError):
|
||||||
|
"""Raised on 5xx server errors."""
|
||||||
|
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user