Fetch issues with pagination and exponential backoff.
(owner: str, repo: str, states: list[str] | None = None)
| 159 | |
| 160 | |
| 161 | def fetch_all_issues(owner: str, repo: str, states: list[str] | None = None) -> list[dict]: |
| 162 | """Fetch issues with pagination and exponential backoff.""" |
| 163 | if states is None: |
| 164 | states = ["OPEN"] |
| 165 | all_issues = [] |
| 166 | cursor = None |
| 167 | page = 1 |
| 168 | max_retries = 5 |
| 169 | label = "/".join(s.lower() for s in states) |
| 170 | |
| 171 | while True: |
| 172 | for attempt in range(max_retries): |
| 173 | try: |
| 174 | print(f"Fetching {label} issues page {page}...", file=sys.stderr) |
| 175 | data = gh_graphql( |
| 176 | GRAPHQL_QUERY, |
| 177 | { |
| 178 | "owner": owner, |
| 179 | "repo": repo, |
| 180 | "cursor": cursor, |
| 181 | "states": states, |
| 182 | }, |
| 183 | ) |
| 184 | break |
| 185 | except RuntimeError as e: |
| 186 | wait = min(2**attempt, 60) |
| 187 | print(f"Error on attempt {attempt + 1}: {e}", file=sys.stderr) |
| 188 | if attempt < max_retries - 1: |
| 189 | print(f"Retrying in {wait}s...", file=sys.stderr) |
| 190 | time.sleep(wait) |
| 191 | else: |
| 192 | raise |
| 193 | |
| 194 | rate = data["data"]["rateLimit"] |
| 195 | print(f" Rate limit: {rate['remaining']} remaining, cost: {rate['cost']}", file=sys.stderr) |
| 196 | |
| 197 | if rate["remaining"] < 100: |
| 198 | reset_at = datetime.fromisoformat(rate["resetAt"].replace("Z", "+00:00")) |
| 199 | wait_seconds = (reset_at - datetime.now(timezone.utc)).total_seconds() + 5 |
| 200 | if wait_seconds > 0: |
| 201 | print(f" Rate limit low, waiting {wait_seconds:.0f}s until reset...", file=sys.stderr) |
| 202 | time.sleep(wait_seconds) |
| 203 | |
| 204 | issues_data = data["data"]["repository"]["issues"] |
| 205 | raw_issues = issues_data["nodes"] |
| 206 | total = issues_data["totalCount"] |
| 207 | |
| 208 | for raw in raw_issues: |
| 209 | all_issues.append(transform_issue(raw)) |
| 210 | |
| 211 | print(f" Fetched {len(all_issues)}/{total} issues", file=sys.stderr) |
| 212 | |
| 213 | page_info = issues_data["pageInfo"] |
| 214 | if not page_info["hasNextPage"]: |
| 215 | break |
| 216 | |
| 217 | cursor = page_info["endCursor"] |
| 218 | page += 1 |
no test coverage detected