0
# Sans-I/O Functions
1
2
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.
3
4
## Capabilities
5
6
### Webhook Validation
7
8
Validate the signature of GitHub webhook events using HMAC with SHA-256 or SHA-1.
9
10
```python { .api }
11
def validate_event(payload: bytes, *, signature: str, secret: str) -> None:
12
"""
13
Validate the signature of a webhook event.
14
15
Parameters:
16
- payload: The raw webhook payload bytes
17
- signature: The signature from X-Hub-Signature or X-Hub-Signature-256 header
18
- secret: The webhook secret configured in GitHub
19
20
Raises:
21
- ValidationFailure: If signature validation fails
22
"""
23
```
24
25
### Request Header Creation
26
27
Create GitHub-specific HTTP headers with proper user agent, API version, and authentication.
28
29
```python { .api }
30
def create_headers(
31
requester: str,
32
*,
33
accept: str = accept_format(),
34
oauth_token: Optional[str] = None,
35
jwt: Optional[str] = None
36
) -> Dict[str, str]:
37
"""
38
Create a dict representing GitHub-specific header fields.
39
40
Parameters:
41
- requester: User agent identifier (username or project name)
42
- accept: Accept header for API version and format
43
- oauth_token: Personal access token for authentication
44
- jwt: JWT bearer token for GitHub App authentication
45
46
Returns:
47
- Dict of lowercased header field names to values
48
49
Raises:
50
- ValueError: If both oauth_token and jwt are provided
51
"""
52
53
def accept_format(
54
*,
55
version: str = "v3",
56
media: Optional[str] = None,
57
json: bool = True
58
) -> str:
59
"""
60
Construct the specification of the format that a request should return.
61
62
Parameters:
63
- version: GitHub API version (default: "v3")
64
- media: Media type for alternative formats
65
- json: Whether to request JSON format
66
67
Returns:
68
- Accept header value
69
"""
70
```
71
72
### Response Processing
73
74
Decode HTTP responses and extract rate limit information and pagination links.
75
76
```python { .api }
77
def decipher_response(
78
status_code: int,
79
headers: Mapping[str, str],
80
body: bytes
81
) -> Tuple[Any, Optional[RateLimit], Optional[str]]:
82
"""
83
Decipher an HTTP response for a GitHub API request.
84
85
Parameters:
86
- status_code: HTTP response status code
87
- headers: HTTP response headers (with lowercase keys)
88
- body: HTTP response body bytes
89
90
Returns:
91
- Tuple of (decoded_body, rate_limit, next_page_url)
92
93
Raises:
94
- HTTPException: For non-success status codes
95
- RateLimitExceeded: When rate limit is exceeded
96
- InvalidField: For 422 responses with field errors
97
- ValidationError: For 422 responses with validation errors
98
"""
99
```
100
101
### URL Formatting
102
103
Construct and expand GitHub API URLs with template variables.
104
105
```python { .api }
106
def format_url(
107
url: str,
108
url_vars: Optional[variable.VariableValueDict],
109
*,
110
base_url: str = DOMAIN
111
) -> str:
112
"""
113
Construct a URL for the GitHub API.
114
115
Parameters:
116
- url: Absolute or relative URL (can be URI template)
117
- url_vars: Variables for URI template expansion
118
- base_url: Base URL for relative URLs (default: https://api.github.com)
119
120
Returns:
121
- Fully-qualified expanded URL
122
"""
123
```
124
125
### Event Processing
126
127
Process GitHub webhook events from HTTP requests.
128
129
```python { .api }
130
class Event:
131
"""Details of a GitHub webhook event."""
132
133
def __init__(self, data: Any, *, event: str, delivery_id: str) -> None:
134
"""
135
Initialize webhook event.
136
137
Parameters:
138
- data: Parsed webhook payload data
139
- event: Event type (e.g., "push", "pull_request")
140
- delivery_id: Unique delivery identifier
141
"""
142
143
@classmethod
144
def from_http(
145
cls,
146
headers: Mapping[str, str],
147
body: bytes,
148
*,
149
secret: Optional[str] = None
150
) -> "Event":
151
"""
152
Construct an event from HTTP headers and JSON body data.
153
154
Parameters:
155
- headers: HTTP headers (with lowercase keys)
156
- body: Raw HTTP body bytes
157
- secret: Webhook secret for validation (optional)
158
159
Returns:
160
- Event instance
161
162
Raises:
163
- BadRequest: For invalid content type
164
- ValidationFailure: For signature validation failures
165
"""
166
167
# Attributes
168
data: Any # Parsed webhook payload
169
event: str # Event type
170
delivery_id: str # Unique delivery ID
171
```
172
173
### Rate Limit Tracking
174
175
Track GitHub API rate limits from HTTP response headers.
176
177
```python { .api }
178
class RateLimit:
179
"""The rate limit imposed upon the requester."""
180
181
def __init__(self, *, limit: int, remaining: int, reset_epoch: float) -> None:
182
"""
183
Instantiate a RateLimit object.
184
185
Parameters:
186
- limit: Rate limit per hour
187
- remaining: Remaining requests in current window
188
- reset_epoch: Reset time in seconds since UTC epoch
189
"""
190
191
def __bool__(self) -> bool:
192
"""True if requests are remaining or the reset datetime has passed."""
193
194
def __str__(self) -> str:
195
"""Provide all details in a reasonable format."""
196
197
@classmethod
198
def from_http(cls, headers: Mapping[str, str]) -> Optional["RateLimit"]:
199
"""
200
Gather rate limit information from HTTP headers.
201
202
Parameters:
203
- headers: HTTP response headers (with lowercase keys)
204
205
Returns:
206
- RateLimit instance or None if headers not found
207
"""
208
209
# Attributes
210
limit: int # Requests per hour limit
211
remaining: int # Remaining requests
212
reset_datetime: datetime.datetime # Reset time (timezone-aware UTC)
213
```
214
215
## Usage Examples
216
217
### Webhook Validation
218
219
```python
220
import gidgethub.sansio
221
222
def handle_webhook(request_headers, request_body, webhook_secret):
223
try:
224
# Validate webhook signature
225
signature = request_headers.get('x-hub-signature-256',
226
request_headers.get('x-hub-signature'))
227
gidgethub.sansio.validate_event(request_body,
228
signature=signature,
229
secret=webhook_secret)
230
231
# Parse webhook event
232
event = gidgethub.sansio.Event.from_http(request_headers,
233
request_body,
234
secret=webhook_secret)
235
236
print(f"Received {event.event} event: {event.delivery_id}")
237
return event
238
239
except gidgethub.ValidationFailure as exc:
240
print(f"Webhook validation failed: {exc}")
241
raise
242
```
243
244
### Manual HTTP Request Processing
245
246
```python
247
import gidgethub.sansio
248
import httpx
249
250
async def make_github_request(url, oauth_token=None):
251
# Create headers
252
headers = gidgethub.sansio.create_headers(
253
"my-app/1.0",
254
oauth_token=oauth_token
255
)
256
257
# Make HTTP request
258
async with httpx.AsyncClient() as client:
259
response = await client.get(url, headers=headers)
260
261
# Process response
262
data, rate_limit, next_page = gidgethub.sansio.decipher_response(
263
response.status_code,
264
dict(response.headers),
265
response.content
266
)
267
268
print(f"Rate limit: {rate_limit}")
269
if next_page:
270
print(f"Next page: {next_page}")
271
272
return data
273
```
274
275
## Constants
276
277
```python { .api }
278
DOMAIN: str = "https://api.github.com" # Default GitHub API base URL
279
```
280
281
## Types
282
283
```python { .api }
284
from typing import Any, Dict, Mapping, Optional, Tuple
285
from uritemplate import variable
286
import datetime
287
288
# Type alias for URI template variables
289
VariableValueDict = variable.VariableValueDict
290
```