GitHub
GitHub API helpers using niquests.
Installation
Dependencies
- niquests - Modern HTTP client with HTTP/3 support
Overview
This module provides an async client for the GitHub REST API:
- Issue and PR comment management (create, delete, idempotent operations)
- Label management (add, remove, list)
- Deployment status management (list, mark inactive)
Authentication
The client uses the GITHUB_TOKEN environment variable by default:
Or pass a token explicitly:
Issue Comments
get_issue_comments(repository, issue_number) -> list[IssueComment]
Get all comments on an issue or PR.
comments = await gh.get_issue_comments("owner/repo", 123)
for c in comments:
print(f"{c['user']['login']}: {c['body']}")
create_issue_comment(repository, issue_number, body) -> IssueComment
Create a comment on an issue or PR.
comment = await gh.create_issue_comment("owner/repo", 123, "Hello from bot!")
print(f"Created comment {comment['id']}")
delete_issue_comment(repository, comment_id) -> None
Delete a comment by ID.
find_comments_with_marker(repository, issue_number, marker) -> list[int]
Find comment IDs containing a specific marker string.
# Find all bot comments
ids = await gh.find_comments_with_marker("owner/repo", 123, "<!-- my-bot -->")
delete_comments_with_marker(repository, issue_number, marker, *, on_progress) -> int
Delete all comments containing a specific marker. Returns the count of deleted comments.
deleted = await gh.delete_comments_with_marker(
"owner/repo", 123, "<!-- preview-bot -->",
on_progress=lambda i, total: print(f"Deleted {i}/{total}")
)
print(f"Removed {deleted} old bot comments")
create_idempotent_comment(repository, issue_number, body, marker) -> IssueComment | None
Create a comment only if one with the marker doesn't already exist. Returns the created comment, or None if skipped.
# Only post once per PR
body = "<!-- ci-status -->\n## Build Status\n..."
comment = await gh.create_idempotent_comment("owner/repo", 123, body, "<!-- ci-status -->")
if comment:
print("Posted new status comment")
else:
print("Status comment already exists")
Labels
get_issue_labels(repository, issue_number) -> list[Label]
Get all labels on an issue or PR.
add_labels(repository, issue_number, labels) -> list[Label]
Add labels to an issue or PR.
remove_label(repository, issue_number, label) -> bool
Remove a label from an issue or PR. Returns True if removed, False if not found.
Deployments
get_deployments(repository, *, environment) -> list[Deployment]
Get deployments, optionally filtered by environment.
# All deployments
deploys = await gh.get_deployments("owner/repo")
# Filter by environment
preview_deploys = await gh.get_deployments("owner/repo", environment="preview-123")
get_deployment_statuses(repository, deployment_id) -> list[DeploymentStatus]
Get all statuses for a deployment, most recent first.
statuses = await gh.get_deployment_statuses("owner/repo", 123456)
for status in statuses:
print(f"{status['state']} at {status['created_at']}")
get_latest_deployment_status(repository, environment) -> DeploymentStatus | None
Get the latest deployment status for an environment. Returns None if no deployments exist.
status = await gh.get_latest_deployment_status("owner/repo", "production")
if status:
print(f"Production is {status['state']}")
create_deployment_status(repository, deployment_id, state, *, description, environment_url) -> DeploymentStatus
Create a deployment status. State can be: error, failure, inactive, in_progress, queued, pending, success.
status = await gh.create_deployment_status(
"owner/repo",
deployment_id=123456,
state="success",
description="Deployed successfully",
environment_url="https://preview-123.example.com",
)
mark_deployment_inactive(repository, environment, *, description, on_progress) -> int
Mark all deployments for an environment as inactive. Returns the count of updated deployments.
# Clean up preview environment when PR is closed
count = await gh.mark_deployment_inactive(
"owner/repo",
"preview-pr-42",
description="PR closed",
on_progress=lambda i, total: print(f"Deactivated {i}/{total}"),
)
print(f"Marked {count} deployments as inactive")
Configuration
Custom Base URL
For GitHub Enterprise:
Retries
Configure automatic retries:
from urllib3.util.retry import Retry
retry = Retry(total=3, backoff_factor=0.5)
async with GitHubClient(retries=retry) as gh:
# ...
Request Hooks
Add custom hooks for logging or metrics:
def log_response(response, **kwargs):
print(f"{response.request.method} {response.url} -> {response.status_code}")
async with GitHubClient(hooks={"response": [log_response]}) as gh:
# ...
Error Handling
The client uses raise_for_status() on all API responses, raising niquests.HTTPError on failures:
from niquests import HTTPError
try:
await gh.create_issue_comment("owner/repo", 999999, "test")
except HTTPError as e:
print(f"GitHub API error: {e.response.status_code} - {e.response.text}")
Types
The module exports TypedDict types generated from GitHub's OpenAPI spec:
IssueComment- Issue/PR comment dataLabel- Label dataDeployment- Deployment dataDeploymentStatus- Deployment status dataProgressCallback- Type alias for progress callbacksCallable[[int, int], None]