From 8fb03be5e51610f0c2c6162270d226453030bcfd Mon Sep 17 00:00:00 2001 From: Dimitris Kargatzis Date: Sun, 1 Mar 2026 21:05:55 +0200 Subject: [PATCH] docs: improve docstring coverage across event processors and models Add comprehensive docstrings to improve project-wide docstring coverage: **src/integrations/github/models.py** (13 docstrings added): - Document all GraphQL response wrapper classes (ReviewConnection, IssueConnection, CommitConnection, FileConnection, etc.) - Clarify purpose of each Pydantic model in API response structure **src/event_processors/deployment_protection_rule.py** (5 docstrings): - Add detailed docstring to process() method explaining the complete deployment approval/rejection workflow with error handling - Document __init__, get_event_type, prepare_webhook_data, and prepare_api_data methods **Event Processors** (12 docstrings total): - deployment_status.py: Document logging-only processor - deployment.py: Document deployment creation logging - check_run.py: Document check_run re-evaluation flow - deployment_review.py: Document review approval handling - All methods: __init__, get_event_type, and process() Total: 30 docstrings added to address ~77% coverage issue. Addresses CodeRabbit pre-merge check warning about insufficient docstring coverage (77.27% vs 80% threshold). --- src/event_processors/check_run.py | 14 +++++ src/event_processors/deployment.py | 14 +++++ .../deployment_protection_rule.py | 63 +++++++++++++++++++ src/event_processors/deployment_review.py | 13 ++++ src/event_processors/deployment_status.py | 13 ++++ src/integrations/github/models.py | 26 ++++++++ 6 files changed, 143 insertions(+) diff --git a/src/event_processors/check_run.py b/src/event_processors/check_run.py index 1a73eed..8e29880 100644 --- a/src/event_processors/check_run.py +++ b/src/event_processors/check_run.py @@ -13,6 +13,7 @@ class CheckRunProcessor(BaseEventProcessor): """Processor for check run events using hybrid agentic rule evaluation.""" def __init__(self) -> None: + """Initialize check run processor with hybrid rule engine agent.""" # Call super class __init__ first super().__init__() @@ -20,9 +21,22 @@ def __init__(self) -> None: self.engine_agent = get_agent("engine") def get_event_type(self) -> str: + """Return the event type this processor handles.""" return "check_run" async def process(self, task: Task) -> ProcessingResult: + """Process check_run event with hybrid rule evaluation. + + Handles check_run events (rerequested, completed) to re-evaluate rules + when checks are re-run. Ignores Watchflow's own check runs to prevent + infinite loops. + + Args: + task: Task containing check_run event payload + + Returns: + ProcessingResult with evaluation results + """ start_time = time.time() payload = task.payload check_run = payload.get("check_run", {}) diff --git a/src/event_processors/deployment.py b/src/event_processors/deployment.py index 6160fc7..18811ce 100644 --- a/src/event_processors/deployment.py +++ b/src/event_processors/deployment.py @@ -12,13 +12,27 @@ class DeploymentProcessor(BaseEventProcessor): """Processor for deployment events - for logging only.""" def __init__(self) -> None: + """Initialize deployment processor for logging purposes.""" # Call super class __init__ first super().__init__() def get_event_type(self) -> str: + """Return the event type this processor handles.""" return "deployment" async def process(self, task: Task) -> ProcessingResult: + """Process deployment event for logging purposes only. + + This processor does not enforce rules - it only logs deployment creation + events for observability. Rule evaluation is handled by + deployment_protection_rule events. + + Args: + task: Task containing deployment event payload + + Returns: + ProcessingResult with success=True (always succeeds) + """ start_time = time.time() payload = task.payload deployment = payload.get("deployment", {}) diff --git a/src/event_processors/deployment_protection_rule.py b/src/event_processors/deployment_protection_rule.py index 7b5f3fd..8e5c8e7 100644 --- a/src/event_processors/deployment_protection_rule.py +++ b/src/event_processors/deployment_protection_rule.py @@ -18,6 +18,7 @@ class DeploymentProtectionRuleProcessor(BaseEventProcessor): """Processor for deployment protection rule events using hybrid agentic rule evaluation.""" def __init__(self): + """Initialize deployment protection rule processor with hybrid rule engine agent.""" # Call super class __init__ first super().__init__() @@ -25,6 +26,7 @@ def __init__(self): self.engine_agent = get_agent("engine") def get_event_type(self) -> str: + """Return the event type this processor handles.""" return "deployment_protection_rule" @staticmethod @@ -36,6 +38,45 @@ def _is_valid_environment(env: str | None) -> bool: return bool(env and isinstance(env, str) and env.strip()) async def process(self, task: Task) -> ProcessingResult: + """Process deployment protection rule event with hybrid rule evaluation. + + This method orchestrates the deployment approval/rejection workflow: + 1. Validates callback URL and environment from webhook payload + 2. Loads deployment rules from repository configuration + 3. Enriches event data with commit/deployment metadata + 4. Evaluates rules using hybrid agent (deterministic + LLM fallback) + 5. Handles time-based scheduling for delayed deployment windows + 6. Approves/rejects deployment via GitHub API callback + 7. Posts check run with evaluation results + + Args: + task: Task containing deployment_protection_rule event payload with: + - deployment: Deployment metadata (id, sha, ref, environment) + - deployment_callback_url: GitHub API endpoint for approval/rejection + - environment: Target deployment environment name + - installation_id: GitHub App installation identifier + - repo_full_name: Repository in owner/name format + + Returns: + ProcessingResult with: + - success: True if deployment was approved/rejected successfully + - violations: List of rule violations that blocked deployment + - api_calls_made: Count of GitHub API calls (approx) + - processing_time_ms: Total processing time in milliseconds + - error: Error message if processing failed + + Side Effects: + - Calls GitHub deployment approval/rejection API + - Creates check run with evaluation details + - Schedules delayed deployment approval via deployment scheduler + - Logs structured events at decision boundaries + + Error Handling: + - Retries approval API calls with exponential backoff (3 attempts) + - Falls back to LLM if deterministic evaluation fails + - Returns success=False with error message on unrecoverable failures + - Gracefully degrades if rules file is missing or malformed + """ start_time = time.time() try: @@ -385,9 +426,31 @@ def _format_violations_comment(violations): return text async def prepare_webhook_data(self, task: Task) -> dict[str, Any]: + """Extract data from webhook payload for rule evaluation. + + Returns the raw payload as-is since deployment_protection_rule events + contain all necessary data (deployment, environment, callback URL). + + Args: + task: Task with deployment_protection_rule payload + + Returns: + Dictionary with deployment event data from webhook + """ return task.payload async def prepare_api_data(self, task: Task) -> dict[str, Any]: + """Fetch additional data via GitHub API for rule evaluation. + + For deployment_protection_rule events, all necessary data is already + in the webhook payload, so no additional API calls are needed. + + Args: + task: Task with deployment_protection_rule payload + + Returns: + Empty dictionary (no additional API data required) + """ return {} def _get_rule_provider(self): diff --git a/src/event_processors/deployment_review.py b/src/event_processors/deployment_review.py index b06af07..98cc7df 100644 --- a/src/event_processors/deployment_review.py +++ b/src/event_processors/deployment_review.py @@ -13,6 +13,7 @@ class DeploymentReviewProcessor(BaseEventProcessor): """Processor for deployment review events using hybrid agentic rule evaluation.""" def __init__(self) -> None: + """Initialize deployment review processor with hybrid rule engine agent.""" # Call super class __init__ first super().__init__() @@ -20,9 +21,21 @@ def __init__(self) -> None: self.engine_agent = get_agent("engine") def get_event_type(self) -> str: + """Return the event type this processor handles.""" return "deployment_review" async def process(self, task: Task) -> ProcessingResult: + """Process deployment_review event with hybrid rule evaluation. + + Handles deployment review approvals/rejections from reviewers after + a deployment protection rule has requested human review. + + Args: + task: Task containing deployment_review event payload + + Returns: + ProcessingResult with evaluation results + """ start_time = time.time() payload = task.payload deployment_review = payload.get("deployment_review", {}) diff --git a/src/event_processors/deployment_status.py b/src/event_processors/deployment_status.py index 5a55c60..31ce6ef 100644 --- a/src/event_processors/deployment_status.py +++ b/src/event_processors/deployment_status.py @@ -12,13 +12,26 @@ class DeploymentStatusProcessor(BaseEventProcessor): """Processor for deployment_status events - for logging and monitoring only.""" def __init__(self) -> None: + """Initialize deployment status processor for logging and monitoring.""" # Call super class __init__ first super().__init__() def get_event_type(self) -> str: + """Return the event type this processor handles.""" return "deployment_status" async def process(self, task: Task) -> ProcessingResult: + """Process deployment_status event for logging and monitoring purposes. + + This processor does not enforce rules - it only logs deployment status + transitions (waiting, success, failure, error) for observability. + + Args: + task: Task containing deployment_status event payload + + Returns: + ProcessingResult with success=True (always succeeds) + """ start_time = time.time() payload = task.payload deployment_status = payload.get("deployment_status", {}) diff --git a/src/integrations/github/models.py b/src/integrations/github/models.py index 57ada3c..8141b78 100644 --- a/src/integrations/github/models.py +++ b/src/integrations/github/models.py @@ -15,6 +15,8 @@ class ReviewNode(BaseModel): class ReviewConnection(BaseModel): + """Wrapper for list of PR review nodes from GraphQL API.""" + nodes: list[ReviewNode] @@ -26,10 +28,14 @@ class IssueNode(BaseModel): class IssueConnection(BaseModel): + """Wrapper for list of linked issue nodes from GraphQL API.""" + nodes: list[IssueNode] class CommitMessage(BaseModel): + """Container for a single commit message.""" + message: str @@ -40,43 +46,61 @@ class CommitNode(BaseModel): class CommitConnection(BaseModel): + """Wrapper for list of commit nodes from GraphQL API.""" + nodes: list[CommitNode] class FileNode(BaseModel): + """Single file path node in GraphQL response.""" + path: str class FileEdge(BaseModel): + """GraphQL edge wrapper for file node.""" + node: FileNode class FileConnection(BaseModel): + """Wrapper for list of file edges from GraphQL API.""" + edges: list[FileEdge] class CommentConnection(BaseModel): + """Wrapper for PR comment count from GraphQL API.""" + model_config = ConfigDict(populate_by_name=True) total_count: int = Field(alias="totalCount") class ThreadCommentNode(BaseModel): + """Single review thread comment from GraphQL API.""" + author: Actor | None body: str createdAt: str class ThreadCommentConnection(BaseModel): + """Wrapper for list of review thread comments from GraphQL API.""" + nodes: list[ThreadCommentNode] class ReviewThreadNode(BaseModel): + """Single review thread with resolution status and comments.""" + isResolved: bool isOutdated: bool comments: ThreadCommentConnection class ReviewThreadConnection(BaseModel): + """Wrapper for list of review thread nodes from GraphQL API.""" + nodes: list[ReviewThreadNode] @@ -110,6 +134,8 @@ class Repository(BaseModel): class GraphQLResponseData(BaseModel): + """GraphQL response data container with repository field.""" + repository: Repository | None