An asynchronous GitHub API library designed as a sans-I/O library for GitHub API access
—
Pure functions for HTTP request/response processing, webhook validation, rate limiting, and URL formatting without performing any I/O operations. This sans-I/O design allows users to choose their preferred HTTP library while gidgethub handles GitHub-specific API details.
Validate the signature of GitHub webhook events using HMAC with SHA-256 or SHA-1.
def validate_event(payload: bytes, *, signature: str, secret: str) -> None:
"""
Validate the signature of a webhook event.
Parameters:
- payload: The raw webhook payload bytes
- signature: The signature from X-Hub-Signature or X-Hub-Signature-256 header
- secret: The webhook secret configured in GitHub
Raises:
- ValidationFailure: If signature validation fails
"""Create GitHub-specific HTTP headers with proper user agent, API version, and authentication.
def create_headers(
requester: str,
*,
accept: str = accept_format(),
oauth_token: Optional[str] = None,
jwt: Optional[str] = None
) -> Dict[str, str]:
"""
Create a dict representing GitHub-specific header fields.
Parameters:
- requester: User agent identifier (username or project name)
- accept: Accept header for API version and format
- oauth_token: Personal access token for authentication
- jwt: JWT bearer token for GitHub App authentication
Returns:
- Dict of lowercased header field names to values
Raises:
- ValueError: If both oauth_token and jwt are provided
"""
def accept_format(
*,
version: str = "v3",
media: Optional[str] = None,
json: bool = True
) -> str:
"""
Construct the specification of the format that a request should return.
Parameters:
- version: GitHub API version (default: "v3")
- media: Media type for alternative formats
- json: Whether to request JSON format
Returns:
- Accept header value
"""Decode HTTP responses and extract rate limit information and pagination links.
def decipher_response(
status_code: int,
headers: Mapping[str, str],
body: bytes
) -> Tuple[Any, Optional[RateLimit], Optional[str]]:
"""
Decipher an HTTP response for a GitHub API request.
Parameters:
- status_code: HTTP response status code
- headers: HTTP response headers (with lowercase keys)
- body: HTTP response body bytes
Returns:
- Tuple of (decoded_body, rate_limit, next_page_url)
Raises:
- HTTPException: For non-success status codes
- RateLimitExceeded: When rate limit is exceeded
- InvalidField: For 422 responses with field errors
- ValidationError: For 422 responses with validation errors
"""Construct and expand GitHub API URLs with template variables.
def format_url(
url: str,
url_vars: Optional[variable.VariableValueDict],
*,
base_url: str = DOMAIN
) -> str:
"""
Construct a URL for the GitHub API.
Parameters:
- url: Absolute or relative URL (can be URI template)
- url_vars: Variables for URI template expansion
- base_url: Base URL for relative URLs (default: https://api.github.com)
Returns:
- Fully-qualified expanded URL
"""Process GitHub webhook events from HTTP requests.
class Event:
"""Details of a GitHub webhook event."""
def __init__(self, data: Any, *, event: str, delivery_id: str) -> None:
"""
Initialize webhook event.
Parameters:
- data: Parsed webhook payload data
- event: Event type (e.g., "push", "pull_request")
- delivery_id: Unique delivery identifier
"""
@classmethod
def from_http(
cls,
headers: Mapping[str, str],
body: bytes,
*,
secret: Optional[str] = None
) -> "Event":
"""
Construct an event from HTTP headers and JSON body data.
Parameters:
- headers: HTTP headers (with lowercase keys)
- body: Raw HTTP body bytes
- secret: Webhook secret for validation (optional)
Returns:
- Event instance
Raises:
- BadRequest: For invalid content type
- ValidationFailure: For signature validation failures
"""
# Attributes
data: Any # Parsed webhook payload
event: str # Event type
delivery_id: str # Unique delivery IDTrack GitHub API rate limits from HTTP response headers.
class RateLimit:
"""The rate limit imposed upon the requester."""
def __init__(self, *, limit: int, remaining: int, reset_epoch: float) -> None:
"""
Instantiate a RateLimit object.
Parameters:
- limit: Rate limit per hour
- remaining: Remaining requests in current window
- reset_epoch: Reset time in seconds since UTC epoch
"""
def __bool__(self) -> bool:
"""True if requests are remaining or the reset datetime has passed."""
def __str__(self) -> str:
"""Provide all details in a reasonable format."""
@classmethod
def from_http(cls, headers: Mapping[str, str]) -> Optional["RateLimit"]:
"""
Gather rate limit information from HTTP headers.
Parameters:
- headers: HTTP response headers (with lowercase keys)
Returns:
- RateLimit instance or None if headers not found
"""
# Attributes
limit: int # Requests per hour limit
remaining: int # Remaining requests
reset_datetime: datetime.datetime # Reset time (timezone-aware UTC)import gidgethub.sansio
def handle_webhook(request_headers, request_body, webhook_secret):
try:
# Validate webhook signature
signature = request_headers.get('x-hub-signature-256',
request_headers.get('x-hub-signature'))
gidgethub.sansio.validate_event(request_body,
signature=signature,
secret=webhook_secret)
# Parse webhook event
event = gidgethub.sansio.Event.from_http(request_headers,
request_body,
secret=webhook_secret)
print(f"Received {event.event} event: {event.delivery_id}")
return event
except gidgethub.ValidationFailure as exc:
print(f"Webhook validation failed: {exc}")
raiseimport gidgethub.sansio
import httpx
async def make_github_request(url, oauth_token=None):
# Create headers
headers = gidgethub.sansio.create_headers(
"my-app/1.0",
oauth_token=oauth_token
)
# Make HTTP request
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
# Process response
data, rate_limit, next_page = gidgethub.sansio.decipher_response(
response.status_code,
dict(response.headers),
response.content
)
print(f"Rate limit: {rate_limit}")
if next_page:
print(f"Next page: {next_page}")
return dataDOMAIN: str = "https://api.github.com" # Default GitHub API base URLfrom typing import Any, Dict, Mapping, Optional, Tuple
from uritemplate import variable
import datetime
# Type alias for URI template variables
VariableValueDict = variable.VariableValueDictInstall with Tessl CLI
npx tessl i tessl/pypi-gidgethub