PyGithub review
PyGithub 2.10.0 is a synchronous, object-oriented wrapper around GitHub's REST API. Repositories, issues, pull requests, workflows, users, and organizations arrive as Python objects, while the client handles authentication, pagination, connection reuse, Enterprise base URLs, retries, and GitHub App installation tokens. Version 2.10.0 drops Python 3.9, adds selectable GitHub API versions, issue dependencies, workflow attempt and dispatch details, search `incomplete_results`, and a maximum rate-limit wait. Its GraphQL method remains a low-level dictionary response. Our wheel was pure Python and typed.
PyGithub 2.10.0 installed in 0.7 seconds and occupied 25 MB across 12 packages in our sandbox, with typed pure-Python code and no audit findings. It fits synchronous REST automation; async or GraphQL-heavy services should choose a client designed for that execution model, and long-lived products should review the LGPL terms and open maintainer request.
We installed it
| Install | ✓ · 0.7s | 12 packages on disk · 25 MB |
| Import | ✓ | import github in 1.05s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does PyGithub install cleanly?
Yes. In a fresh container with an empty cache, pip install PyGithub finished in 0.7s, leaving 12 packages and 25 MB on disk. pip-audit reported no known vulnerabilities.
What does PyGithub need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import github succeeded in 1.05s, and the package ships py.typed for type checkers.
PyGithub or GitHubKit: which should you use?
GitHubKit: Use it when one typed library must cover async and sync REST calls plus GraphQL. PyGithub 2.10.0 installed in 0.7 seconds and occupied 25 MB across 12 packages in our sandbox, with typed pure-Python code and no audit findings.
When should you not use PyGithub?
The service is asyncio-based and makes concurrent GitHub requests. PyGithub blocks its caller; GitHubKit supports async execution.
Use it if
- A synchronous Python job performs several GitHub REST tasks and benefits from Repository, Issue, PullRequest, and WorkflowRun objects.
- The program should iterate paginated REST collections without implementing Link-header traversal itself.
- A GitHub App needs JWT signing, installation-token refresh, and a client scoped to each installation.
- The same automation must switch between GitHub.com and GitHub Enterprise Server through a REST base URL.
- The service is asyncio-based and makes concurrent GitHub requests. PyGithub blocks its caller; GitHubKit supports async execution.
- GraphQL is the primary interface. PyGithub's requester returns raw GraphQL dictionaries without its REST object or pagination model.
- Every newly announced REST endpoint must be available immediately. PyGithub maintains handwritten or generated class coverage behind GitHub's evolving API.
- Dependency policy excludes LGPL code. The installed package identifies the GNU Library or Lesser General Public License.
- Maintainer capacity is a procurement requirement. The README explicitly asks for people to triage, review pull requests, and cut releases.
Setup reality
We installed PyGithub 2.10.0 in a fresh Python 3.12 Bookworm sandbox. pip finished in 0.7 seconds and left 12 packages using 25 MB. The package declares five direct dependencies, requires Python 3.10 or later, is pure Python, and includes py.typed. import github succeeded in 1.05 seconds. pip-audit found zero known vulnerabilities. The distribution identifies the GNU Library or Lesser General Public License.
Authentication now belongs in Auth.Token, Auth.AppAuth, or another Auth class; old positional token forms are deprecated. Store the token or private key outside source and grant only required repository or organization permissions. Enterprise clients need the REST base URL, commonly ending in /api/v3, plus an API version compatible with that server. A 404 can mean the resource is private and the token lacks permission, so it does not prove absence.
Paginated lists fetch pages during iteration, and lazy objects can make their first request when a property is read. A harmless-looking loop can therefore issue one page request plus many object completion requests. Use slices when only the first results matter, select per_page deliberately, and inspect 2.10's incomplete_results on searches. GitHub search also caps some result sets; partition an inventory rather than presenting totalCount as a complete audit without checking endpoint semantics.
PyGithub is synchronous and adds request or write throttling. Version 2.10.0 adds max_rate_limit_wait, allowing a batch job to fail instead of sleeping past its deadline. Keep rate-limit retries bounded, especially for secondary limits, and do not remove default spacing without replacement logic. Reuse the client for pooled connections, then call close() or use a context manager. Async servers should offload calls to workers or choose an async-native client.
Patterns
Authenticate and close the client token-authentication
import os
from github import Auth, Github
auth = Auth.Token(os.environ['GITHUB_TOKEN'])
with Github(auth=auth) as client:
for repo in client.get_user().get_repos():
print(repo.full_name)Version 2.10 expects an Auth object. The older positional-token constructor is deprecated; keep the token out of source and use minimal permissions.
Connect to GitHub Enterprise Server enterprise-client
import os
from github import Auth, Github
client = Github(
base_url='https://ghe.example.com/api/v3',
auth=Auth.Token(os.environ['GHE_TOKEN']),
api_version='2022-11-28',
)The REST base URL usually includes `/api/v3`. Match the selected API version and TLS trust to the Enterprise Server release.
Create a GitHub App installation client github-app-installation
from pathlib import Path
from github import Auth, GithubIntegration
app_auth = Auth.AppAuth(
app_id=123456,
private_key=Path('/run/secrets/github-app.pem').read_text(),
)
integration = GithubIntegration(auth=app_auth)
client = integration.get_github_for_installation(987654)AppAuth signs the application JWT; the installation client obtains and refreshes the repository-scoped installation token.
Slice a paginated issue list limit-pagination
repo = client.get_repo('PyGithub/PyGithub')
open_issues = repo.get_issues(state='open')
for issue in open_issues[:20]:
print(issue.number, issue.title)Iteration can fetch the whole collection. A short slice limits calls; add `is:issue` to searches when pull requests must be excluded.
Open and update an issue create-issue
repo = client.get_repo('owner/project')
issue = repo.create_issue(
title='Nightly build failed',
body='The failing run is linked in the log.',
labels=['bug', 'ci'],
assignees=['octocat'],
)
issue.create_comment('Retry produced the same failure.')
issue.edit(state='closed', state_reason='completed')Labels must already exist and the token needs issue write permission. Pull requests also appear through GitHub issue endpoints.
Update a file with its current blob SHA update-file
repo = client.get_repo('owner/project')
current = repo.get_contents('README.md', ref='main')
text = current.decoded_content.decode('utf-8')
repo.update_file(
path=current.path,
message='docs: fix typo',
content=text.replace('teh', 'the'),
sha=current.sha,
branch='main',
)A concurrent edit makes the SHA stale and GitHub rejects the write. Fetch again instead of overwriting the newer blob.
Create a branch and draft pull request open-pull-request
repo = client.get_repo('owner/project')
base = repo.get_branch('main')
repo.create_git_ref(
ref='refs/heads/bot/update',
sha=base.commit.sha,
)
pr = repo.create_pull(
base='main',
head='bot/update',
title='chore: update generated data',
body='Automated update.',
draft=True,
)Git references need the full `refs/heads/` prefix. Handle an existing branch before retrying an interrupted automation run.
Search only real issues search-issues
results = client.search_issues(
'repo:PyGithub/PyGithub is:issue is:open label:bug'
)
print(results.totalCount)
for issue in results[:10]:
print(issue.number, issue.title)GitHub search has its own rate-limit bucket and can cap or mark results incomplete. Version 2.10 exposes `incomplete_results` for that check.
Read core and search budgets rate-limits
limits = client.get_rate_limit()
print(limits.resources.core.remaining)
print(limits.resources.core.reset)
print(limits.resources.search.remaining)Current 2.x releases place buckets under `resources`; older examples using `limits.core` no longer match the returned object.
Handle GitHub failure classes classify-api-errors
from github import (
BadCredentialsException,
GithubException,
RateLimitExceededException,
UnknownObjectException,
)
try:
repo = client.get_repo('owner/private-project')
except UnknownObjectException:
report_missing_or_hidden()
except RateLimitExceededException:
reschedule()
except BadCredentialsException:
rotate_token()
except GithubException as error:
report_api_error(error.status, error.data)GitHub may return 404 for a private resource hidden from the token, so UnknownObjectException does not establish that it does not exist.
Dispatch a workflow and request run details dispatch-workflow
repo = client.get_repo('owner/project')
workflow = repo.get_workflow('deploy.yml')
run = workflow.create_dispatch(
ref='main',
inputs={'environment': 'staging'},
throw=True,
return_run_details=True,
)
print(run.id, run.html_url)Version 2.10 can return run details. The workflow needs `workflow_dispatch`, and the token needs Actions write permission.
Stop rate-limit waits at a job deadline bounded-rate-retry
import os
from github import Auth, Github, GithubRetry
retry = GithubRetry(total=5, max_rate_limit_wait=120)
client = Github(
auth=Auth.Token(os.environ['GITHUB_TOKEN']),
retry=retry,
per_page=100,
lazy=True,
)Version 2.10 raises when the required rate-limit sleep exceeds 120 seconds. Lazy properties can still trigger requests later inside loops.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| GitHubKit | PyPI | Use it when one typed library must cover async and sync REST calls plus GraphQL. |
| ghapi | PyPI | Choose it for a thinner endpoint-shaped interface generated from GitHub's API description. |
| gidgethub | PyPI | Use it for asynchronous GitHub integrations when its lower-level API and transport model fit the service. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

