Post

python-github-plus: A Production-Ready GitHub Client with Service-Based Architecture

python-github-plus extends PyGitHub with a clean service-based architecture for branch management, PR workflows, workflow automation, file operations, and tag management — all with robust error handling.

python-github-plus: A Production-Ready GitHub Client with Service-Based Architecture

The official pygithub package gives you raw API access — which is fine until you’re writing automation scripts that need to open a PR, trigger a workflow, wait for it to finish, and then tag a release. At that point you’re assembling a lot of low-level calls into something that should be a three-line script. python-github-plus gives you a structured, production-ready client organized around the services you actually use: branches, pull requests, workflows, files, and tags.

Why I Built This

I was writing a CI automation script that needed to create a branch, push a commit, open a PR, trigger a workflow, wait for it to pass, and merge — all programmatically. With raw pygithub, each of those was a handful of API calls with no consistent error handling between them. I kept rebuilding the same service wrappers. Eventually I extracted them into a proper library with a clean namespace for each concern.

Installation

1
pip install python-github-plus
1
uv add python-github-plus

Setup & Configuration

1
2
export GitHub_ACCESS_TOKEN=your_github_access_token
export GitHub_URL=https://github.com  # defaults to github.com
1
2
3
4
5
6
from python_github_plus import GitHubClient

github_client = GitHubClient(
    access_token="your_access_token",
    repo_full_name="your-org/your-repo",
)

Six services, one client, clean namespaces:

1
2
3
4
5
6
github_client.project       # project info & members
github_client.branch        # branch lifecycle
github_client.pull_request  # PR workflows
github_client.workflow      # GitHub Actions
github_client.tag           # tag management
github_client.file          # file operations

Quick Start

The full branch → PR → merge flow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Create a feature branch
github_client.branch.create(branch_name="feature/new-feature", from_branch="main")

# Open a PR
pr = github_client.pull_request.create(
    title="Add new feature",
    from_branch="feature/new-feature",
    target="main"
)

# Review and merge
github_client.pull_request.add_comment(pr.number, "LGTM!")
github_client.pull_request.approve(pr.number)
github_client.pull_request.merge(pr.number)

Real-World Example

Here’s a full release automation script — trigger CI, wait for it to finish, tag the release, clean up the branch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import time
from python_github_plus import GitHubClient

client = GitHubClient(
    access_token=os.getenv("GitHub_ACCESS_TOKEN"),
    repo_full_name="your-org/your-repo",
)

# Trigger CI workflow on the release branch
workflow = client.workflow.trigger(
    workflow_name="CI",
    branch_name="release/v2.0.0",
)

time.sleep(5)  # let the run register
run = client.workflow.last_run_by_id(workflow_id=workflow.id)

# Block until CI finishes
final_status = client.workflow.wait_until_finished(
    run_id=run.id,
    check_interval=30,
    timeout=3600,
)

if final_status == "success":
    # Tag the release
    client.tag.create(
        tag_name="v2.0.0",
        from_branch="release/v2.0.0",
        message="Release version 2.0.0",
    )
    # Merge and clean up
    pr = client.pull_request.create(
        title="Release v2.0.0 → main",
        from_branch="release/v2.0.0",
        target="main",
    )
    client.pull_request.merge(pr.number)
    client.branch.delete("release/v2.0.0")
else:
    print(f"CI failed with status: {final_status}")

What would be 40+ lines of pygithub calls with scattered error handling becomes a linear, readable script.

Key Features

Each capability lives in its own service — branch, pull_request, workflow, tag, file, project — so you import only what you need and the namespace makes every call self-documenting.

Branch management covers the full lifecycle: create from any base, list, protect, and delete. PR workflows cover create, assign, comment, approve, and merge. The workflow service is particularly useful for CI orchestration — trigger() fires a GitHub Actions workflow, wait_until_finished() blocks with a configurable poll interval and timeout, and last_run_by_id() gives you the run handle to track:

1
2
3
4
5
6
# Protect a branch after creating it
client.branch.protect("main")

# Assign and comment on a PR
client.pull_request.assign(pr.number, "username")
client.pull_request.add_comment(pr.number, "Deploying to staging...")

Tag management handles release tagging cleanly — create with a message, list all tags, delete old ones. File operations let you fetch content at any ref:

1
content = client.file.fetch_content(file_path="config/settings.yaml", ref="main")

Project-level operations round out the service set — get repo info, list members, add and remove collaborators.

Goes Well With

  • python-vault — store Telegram and Notion tokens in Vault instead of a plain .env
  • python-jira-plus — I pair these in ticket automation scripts: close the Jira ticket when the PR merges
  • custom-python-logger — logging wired into every service call for audit trails in automation scripts

Everything you need to automate a GitHub workflow — without reading the API docs every time.

This post is licensed under CC BY 4.0 by the author.