PyGithub Authentication for Repositories, Issues, and Pull Requests
python pygithub authentication repositories issues and pull requests: Learn how to authenticate with PyGithub using tokens, then fetch repositories, manage issues, and...
python pygithub authentication repositories issues and pull requests requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you need to automate GitHub interactions from Python, PyGithub is the most direct way to work with the GitHub REST API. The library handles HTTP requests, pagination, and object mapping, but the first hurdle is authentication. Without a valid credential, every call fails with a BadCredentialsException. This article focuses on the practical path from authentication to working with repositories, issues, and pull requests using PyGithub.
Choosing an Authentication Method
PyGithub supports three main authentication mechanisms: personal access tokens, GitHub App tokens, and OAuth tokens. For most automation scripts, a personal access token (PAT) is the simplest choice. A PAT is a string that you generate from GitHub settings and pass to the Github constructor.
from github import Github g = Github("your_personal_access_token")
The token is used as the login credential. If you have a token that only has access to public repositories, that's all you can interact with. For private repositories, the token must have the repo scope. GitHub App authentication requires a JWT and installation token, which is more complex and suited for multi-user integrations. OAuth tokens are used when you act on behalf of a user in a web application.
For a one-off script or a scheduled job, a PAT is sufficient. The token should be stored in an environment variable or a secrets manager, not hardcoded in the source file.
Accessing Repositories
Once authenticated, the Github object gives you access to the authenticated user and repositories. To fetch a specific repository, use get_repo with the full name in the format owner/repo.
repo = g.get_repo("octocat/Hello-World") print(repo.full_name) print(repo.clone_url)
If you want to list repositories for the authenticated user, call get_user().get_repos(). This returns a paginated list, so you can iterate over it directly.
user = g.get_user() for repo in user.get_repos(): print(repo.name, repo.html_url)
You can also filter by type, such as all, owner, member, or public. For example, to get only repositories the user owns:
for repo in user.get_repos(type="owner"): print(repo.name)
When you have a repository object, you can access its attributes like description, language, stargazers_count, and open_issues_count. These are populated from the API response, so no extra requests are needed unless you explicitly call a method that fetches additional data.
Fetching and Creating Issues
Issues are a core part of repository management. PyGithub provides a get_issues method on the repository object. By default, it returns open issues sorted by creation date in descending order. You can pass parameters like state, labels, assignee, and since to filter.
open_issues = repo.get_issues(state="open") for issue in open_issues: print(issue.number, issue.title)
Creating an issue is straightforward with create_issue. You provide a title and optionally a body, labels, assignees, and milestones.
issue = repo.create_issue( title="Fix flaky test", body="The test fails intermittently on CI.", labels=["bug"], assignees=["octocat"] ) print(issue.html_url)
Note that labels must already exist in the repository. If you pass a label that doesn't exist, PyGithub raises a GithubException with a 422 status. You can check available labels with repo.get_labels().
Handling Pull Requests
Pull requests are issues with a diff. PyGithub exposes them through get_pulls and create_pull. The get_pulls method accepts state (open, closed, all), sort, direction, and head/base to filter by branch.
pulls = repo.get_pulls(state="open", sort="created", direction="asc") for pr in pulls: print(pr.number, pr.title, pr.user.login)
To create a pull request, you need the head branch, the base branch, and a title. The body is optional.
pr = repo.create_pull( title="Add new feature", body="This PR adds the new endpoint.", head="feature-branch", base="main" )
The head branch must exist in the repository or in a fork if you specify the owner with owner:branch. If the branches are not comparable, GitHub returns a 422 error.
Once you have a pull request object, you can merge it with merge(). This method accepts a commit message and merge method (merge, squash, rebase).
pr.merge(commit_message="Merge feature branch", merge_method="squash")
Handling Errors and Rate Limits
The GitHub API enforces rate limits based on authentication. For unauthenticated requests, the limit is 60 per hour. With a token, it's 5,000 per hour for most endpoints. PyGithub exposes rate limit information through the get_rate_limit method.
rate_limit = g.get_rate_limit() print(rate_limit.core.limit, rate_limit.core.remaining)
When you exceed the limit, the API returns a 403 response, and PyGithub raises a RateLimitExceededException. You should catch this and either wait or use a token with a higher quota. For scripts that run frequently, consider using conditional requests or caching responses.
Other common exceptions include BadCredentialsException (401), UnknownObjectException (404), and GithubException for general API errors. Always wrap API calls in try-except blocks to handle transient failures gracefully.
from github import GithubException, BadCredentialsException try: repo = g.get_repo("some/nonexistent") except BadCredentialsException: print("Invalid token") except GithubException as e: print(f"API error: {e.status} {e.data.get('message')}")
Security and Token Management
Hardcoding a token in a script is a security risk. If the script is committed to a repository, the token becomes public. Always read tokens from environment variables or a configuration file outside version control.
import os from github import Github token = os.environ.get("GITHUB_TOKEN") if not token: raise ValueError("GITHUB_TOKEN environment variable not set") g = Github(token)
For GitHub Actions, you can use the built-in GITHUB_TOKEN secret. For local development, use a .env file and a library like python-dotenv. Never print the token to logs or error messages.
Additionally, limit the token's permissions to the minimum required. If you only need to read public repositories, don't grant write access. GitHub PATs allow you to select specific scopes; choose only what the script actually uses.
A Complete Example: Automating Issue Triage
Combining authentication, repository access, and issue handling, you can build a script that triages new issues. For example, the following script fetches open issues without a label, adds a triage label, and assigns them to a default reviewer.
import os from github import Github g = Github(os.environ["GITHUB_TOKEN"]) repo = g.get_repo("your-org/your-repo") for issue in repo.get_issues(state="open", labels=[]): if not issue.pull_request: # Skip PRs issue.add_to_labels("triage") issue.edit(assignees=["default-reviewer"]) print(f"Triaged issue #{issue.number}")
This script iterates over all open issues that have no labels. The labels=[] parameter filters for issues with zero labels. The issue.pull_request attribute is None for issues and a dict for pull requests, so you can skip PRs. The add_to_labels method adds a label, and edit updates the assignee.
Note that get_issues with labels=[] may not work as expected on older versions of PyGithub. In some versions, passing an empty list returns all issues regardless of labels. If that happens, filter manually:
for issue in repo.get_issues(state="open"): if not issue.labels and not issue.pull_request: issue.add_to_labels("triage")
This manual check is more reliable across versions. It also avoids an extra API call with a filter that might be ignored.
Rate Limit Awareness in Long-Running Scripts
When processing many issues or PRs, you can easily hit the rate limit. Each get_issues call returns a page of 30 items by default, and each item access may trigger additional API calls if you access lazy-loaded attributes. For example, issue.labels is loaded from the issue data, but issue.user may require an extra request if not included in the initial payload. To minimize API calls, use the per_page parameter to fetch larger pages.
issues = repo.get_issues(state="open", per_page=100)
This reduces the number of HTTP requests. Also, avoid calling issue.edit or add_to_labels in a tight loop unless necessary; batch operations where possible. If you expect to exceed the rate limit, implement a retry with exponential backoff using the Retry class from the urllib3 library, which PyGithub uses internally.
from urllib3.util.retry import Retry from github import Github retry = Retry(total=5, backoff_factor=1, status_forcelist=[403, 500]) g = Github(token, retry=retry)
This configuration makes PyGithub retry failed requests up to five times with a delay. However, be cautious: retrying on 403 may not help if the rate limit is exhausted; you should check the X-RateLimit-Remaining header or the rate limit object before making large batches of calls.