mcp-ticketer 0.2.0__py3-none-any.whl → 2.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +58 -16
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +421 -0
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1284
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -1895
- mcp_ticketer-0.2.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.2.0.dist-info/RECORD +0 -58
- mcp_ticketer-0.2.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""HTTP and GraphQL client for GitHub API.
|
|
2
|
+
|
|
3
|
+
This module provides a unified client for both REST API v3 and GraphQL API v4,
|
|
4
|
+
with error handling, retry logic, and rate limiting support.
|
|
5
|
+
|
|
6
|
+
Design Decision: Dual API Client
|
|
7
|
+
---------------------------------
|
|
8
|
+
GitHub requires both REST and GraphQL APIs:
|
|
9
|
+
- REST API: For mutations (create/update/delete) and simple operations
|
|
10
|
+
- GraphQL API: For complex queries and efficient data fetching
|
|
11
|
+
|
|
12
|
+
Rationale:
|
|
13
|
+
- REST API is more stable and better documented for write operations
|
|
14
|
+
- GraphQL API reduces over-fetching and enables precise field selection
|
|
15
|
+
- Some features only available in one API (e.g., Projects V2 in GraphQL)
|
|
16
|
+
|
|
17
|
+
Trade-offs:
|
|
18
|
+
- Maintaining two client methods increases complexity
|
|
19
|
+
- GraphQL requires fragment management and query composition
|
|
20
|
+
- REST API can be chatty (multiple round trips for related data)
|
|
21
|
+
|
|
22
|
+
Error Handling Strategy:
|
|
23
|
+
-----------------------
|
|
24
|
+
- HTTP 401/403 → Authentication errors (fail fast)
|
|
25
|
+
- HTTP 429 → Rate limiting (could retry with backoff, currently fails fast)
|
|
26
|
+
- HTTP 5xx → Transient errors (fail fast, caller can retry)
|
|
27
|
+
- GraphQL errors → Validation errors (fail fast)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
import httpx
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GitHubClient:
|
|
41
|
+
"""GitHub API client supporting both REST v3 and GraphQL v4.
|
|
42
|
+
|
|
43
|
+
This client handles:
|
|
44
|
+
- Authentication with Personal Access Tokens (PAT)
|
|
45
|
+
- REST API requests with proper headers
|
|
46
|
+
- GraphQL query execution
|
|
47
|
+
- Error handling and HTTP status codes
|
|
48
|
+
- Rate limit tracking (optional)
|
|
49
|
+
|
|
50
|
+
Performance Notes:
|
|
51
|
+
-----------------
|
|
52
|
+
- Uses httpx.AsyncClient for async/await support
|
|
53
|
+
- Connection pooling handled by httpx
|
|
54
|
+
- Timeout: 30 seconds default
|
|
55
|
+
- No automatic retries (caller should implement retry logic)
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
-------
|
|
59
|
+
client = GitHubClient(
|
|
60
|
+
token="ghp_xxxxx",
|
|
61
|
+
owner="octocat",
|
|
62
|
+
repo="hello-world"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# REST API
|
|
66
|
+
response = await client.execute_rest("GET", "repos/octocat/hello-world")
|
|
67
|
+
|
|
68
|
+
# GraphQL API
|
|
69
|
+
data = await client.execute_graphql(query, variables)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
token: str,
|
|
75
|
+
owner: str,
|
|
76
|
+
repo: str,
|
|
77
|
+
api_url: str = "https://api.github.com",
|
|
78
|
+
timeout: float = 30.0,
|
|
79
|
+
):
|
|
80
|
+
"""Initialize GitHub API client.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
----
|
|
84
|
+
token: GitHub Personal Access Token (PAT)
|
|
85
|
+
owner: Repository owner (username or organization)
|
|
86
|
+
repo: Repository name
|
|
87
|
+
api_url: Base API URL (default: https://api.github.com)
|
|
88
|
+
timeout: Request timeout in seconds (default: 30.0)
|
|
89
|
+
"""
|
|
90
|
+
self.token = token
|
|
91
|
+
self.owner = owner
|
|
92
|
+
self.repo = repo
|
|
93
|
+
self.api_url = api_url
|
|
94
|
+
|
|
95
|
+
# GraphQL endpoint
|
|
96
|
+
self.graphql_url = (
|
|
97
|
+
f"{api_url}/graphql"
|
|
98
|
+
if "github.com" in api_url
|
|
99
|
+
else f"{api_url}/api/graphql"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# HTTP headers for authentication and API version
|
|
103
|
+
self.headers = {
|
|
104
|
+
"Authorization": f"Bearer {token}",
|
|
105
|
+
"Accept": "application/vnd.github.v3+json",
|
|
106
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Async HTTP client with connection pooling
|
|
110
|
+
self.client = httpx.AsyncClient(
|
|
111
|
+
base_url=api_url,
|
|
112
|
+
headers=self.headers,
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Rate limit tracking (optional, populated on API responses)
|
|
117
|
+
self._rate_limit: dict[str, Any] = {}
|
|
118
|
+
|
|
119
|
+
async def execute_rest(
|
|
120
|
+
self,
|
|
121
|
+
method: str,
|
|
122
|
+
endpoint: str,
|
|
123
|
+
json_data: dict[str, Any] | None = None,
|
|
124
|
+
params: dict[str, Any] | None = None,
|
|
125
|
+
) -> dict[str, Any] | list[Any]:
|
|
126
|
+
"""Execute a REST API request.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
----
|
|
130
|
+
method: HTTP method (GET, POST, PATCH, DELETE)
|
|
131
|
+
endpoint: API endpoint (e.g., "repos/owner/repo/issues")
|
|
132
|
+
json_data: Optional JSON body for POST/PATCH requests
|
|
133
|
+
params: Optional query parameters
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
-------
|
|
137
|
+
Parsed JSON response (dict or list)
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
------
|
|
141
|
+
httpx.HTTPStatusError: On HTTP error status codes
|
|
142
|
+
ValueError: On invalid JSON response
|
|
143
|
+
|
|
144
|
+
Performance:
|
|
145
|
+
-----------
|
|
146
|
+
Time Complexity: O(1) for request, O(n) for JSON parsing
|
|
147
|
+
Expected Latency: 100-500ms depending on GitHub API response time
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
-------
|
|
151
|
+
# GET request
|
|
152
|
+
issues = await client.execute_rest("GET", "repos/owner/repo/issues")
|
|
153
|
+
|
|
154
|
+
# POST request
|
|
155
|
+
issue = await client.execute_rest(
|
|
156
|
+
"POST",
|
|
157
|
+
"repos/owner/repo/issues",
|
|
158
|
+
json_data={"title": "Bug", "body": "Description"}
|
|
159
|
+
)
|
|
160
|
+
"""
|
|
161
|
+
# Ensure endpoint starts with / for proper URL construction
|
|
162
|
+
if not endpoint.startswith("/"):
|
|
163
|
+
endpoint = f"/{endpoint}"
|
|
164
|
+
|
|
165
|
+
logger.debug(f"REST {method} {endpoint}")
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
response = await self.client.request(
|
|
169
|
+
method=method,
|
|
170
|
+
url=endpoint,
|
|
171
|
+
json=json_data,
|
|
172
|
+
params=params,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Update rate limit information from headers
|
|
176
|
+
self._update_rate_limit_from_headers(response.headers)
|
|
177
|
+
|
|
178
|
+
# Raise for HTTP errors (4xx, 5xx)
|
|
179
|
+
response.raise_for_status()
|
|
180
|
+
|
|
181
|
+
# Parse JSON response
|
|
182
|
+
if response.text:
|
|
183
|
+
return response.json()
|
|
184
|
+
return {}
|
|
185
|
+
|
|
186
|
+
except httpx.HTTPStatusError as e:
|
|
187
|
+
logger.error(
|
|
188
|
+
f"REST API error: {method} {endpoint} -> "
|
|
189
|
+
f"HTTP {e.response.status_code}: {e.response.text}"
|
|
190
|
+
)
|
|
191
|
+
# Let the error propagate - adapter should handle error translation
|
|
192
|
+
raise
|
|
193
|
+
|
|
194
|
+
async def execute_graphql(
|
|
195
|
+
self,
|
|
196
|
+
query: str,
|
|
197
|
+
variables: dict[str, Any] | None = None,
|
|
198
|
+
) -> dict[str, Any]:
|
|
199
|
+
"""Execute a GraphQL query or mutation.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
----
|
|
203
|
+
query: GraphQL query string (including fragments)
|
|
204
|
+
variables: Optional query variables
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
-------
|
|
208
|
+
GraphQL response data (dict)
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
------
|
|
212
|
+
httpx.HTTPStatusError: On HTTP error status codes
|
|
213
|
+
ValueError: On GraphQL errors in response
|
|
214
|
+
|
|
215
|
+
Performance:
|
|
216
|
+
-----------
|
|
217
|
+
Time Complexity: O(1) for request, O(n) for JSON parsing
|
|
218
|
+
Expected Latency: 100-500ms depending on query complexity
|
|
219
|
+
Token Usage: Depends on fragments used (compact vs. full)
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
-------
|
|
223
|
+
data = await client.execute_graphql(
|
|
224
|
+
query=GET_ISSUE,
|
|
225
|
+
variables={"owner": "octocat", "repo": "hello-world", "number": 1}
|
|
226
|
+
)
|
|
227
|
+
issue = data["repository"]["issue"]
|
|
228
|
+
"""
|
|
229
|
+
logger.debug(f"GraphQL query: {query[:100]}...")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
response = await self.client.post(
|
|
233
|
+
self.graphql_url,
|
|
234
|
+
json={"query": query, "variables": variables or {}},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Update rate limit information
|
|
238
|
+
self._update_rate_limit_from_headers(response.headers)
|
|
239
|
+
|
|
240
|
+
# Raise for HTTP errors
|
|
241
|
+
response.raise_for_status()
|
|
242
|
+
|
|
243
|
+
# Parse JSON response
|
|
244
|
+
data = response.json()
|
|
245
|
+
|
|
246
|
+
# Check for GraphQL-level errors
|
|
247
|
+
if "errors" in data:
|
|
248
|
+
error_messages = [
|
|
249
|
+
err.get("message", str(err)) for err in data["errors"]
|
|
250
|
+
]
|
|
251
|
+
raise ValueError(f"GraphQL errors: {', '.join(error_messages)}")
|
|
252
|
+
|
|
253
|
+
return data.get("data", {})
|
|
254
|
+
|
|
255
|
+
except httpx.HTTPStatusError as e:
|
|
256
|
+
logger.error(
|
|
257
|
+
f"GraphQL API error: HTTP {e.response.status_code}: {e.response.text}"
|
|
258
|
+
)
|
|
259
|
+
raise
|
|
260
|
+
|
|
261
|
+
async def test_connection(self) -> bool:
|
|
262
|
+
"""Test GitHub API connection and authentication.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
-------
|
|
266
|
+
True if connection is valid, False otherwise
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
-------
|
|
270
|
+
if await client.test_connection():
|
|
271
|
+
print("GitHub connection successful")
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
await self.execute_rest("GET", "/user")
|
|
275
|
+
return True
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"Connection test failed: {e}")
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
async def get_rate_limit(self) -> dict[str, Any]:
|
|
281
|
+
"""Get current rate limit status.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
-------
|
|
285
|
+
Rate limit information dict with keys:
|
|
286
|
+
- limit: Maximum requests per hour
|
|
287
|
+
- remaining: Remaining requests
|
|
288
|
+
- reset: Unix timestamp when limit resets
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
-------
|
|
292
|
+
rate_limit = await client.get_rate_limit()
|
|
293
|
+
print(f"Remaining: {rate_limit['remaining']}/{rate_limit['limit']}")
|
|
294
|
+
"""
|
|
295
|
+
response = await self.execute_rest("GET", "/rate_limit")
|
|
296
|
+
return response.get("rate", {})
|
|
297
|
+
|
|
298
|
+
def _update_rate_limit_from_headers(self, headers: httpx.Headers) -> None:
|
|
299
|
+
"""Extract rate limit info from response headers.
|
|
300
|
+
|
|
301
|
+
GitHub includes rate limit information in response headers:
|
|
302
|
+
- X-RateLimit-Limit: Maximum requests per hour
|
|
303
|
+
- X-RateLimit-Remaining: Remaining requests
|
|
304
|
+
- X-RateLimit-Reset: Unix timestamp when limit resets
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
----
|
|
308
|
+
headers: HTTP response headers
|
|
309
|
+
"""
|
|
310
|
+
if "X-RateLimit-Limit" in headers:
|
|
311
|
+
self._rate_limit = {
|
|
312
|
+
"limit": int(headers["X-RateLimit-Limit"]),
|
|
313
|
+
"remaining": int(headers.get("X-RateLimit-Remaining", 0)),
|
|
314
|
+
"reset": int(headers.get("X-RateLimit-Reset", 0)),
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async def close(self) -> None:
|
|
318
|
+
"""Close the HTTP client and cleanup resources.
|
|
319
|
+
|
|
320
|
+
Should be called when adapter is no longer needed to avoid
|
|
321
|
+
resource leaks.
|
|
322
|
+
|
|
323
|
+
Example:
|
|
324
|
+
-------
|
|
325
|
+
await client.close()
|
|
326
|
+
"""
|
|
327
|
+
await self.client.aclose()
|
|
328
|
+
|
|
329
|
+
async def __aenter__(self):
|
|
330
|
+
"""Async context manager entry."""
|
|
331
|
+
return self
|
|
332
|
+
|
|
333
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
334
|
+
"""Async context manager exit - ensures cleanup."""
|
|
335
|
+
await self.close()
|