Action0-GitHub-API
A fully typed GitHub REST API client
built on action0-client:
GitHub endpoints are described once, as typed operation dataclasses, and
run synchronously, on asyncio or on Twisted — decided by the backend you
plug in. The type checker follows along: the same send() returns a
value, an Awaitable or a Deferred, depending on the backend.
client = GitHubClient(RequestsBackend())
repo = client.send(GetRepo(owner="python", repo="cpython")) # Repo
client = GitHubClient(AsyncHttpxBackend())
repo = await client.send(GetRepo(owner="python", repo="cpython")) # Awaitable[Repo]
client = GitHubClient(TwistedBackend())
deferred = client.send(GetRepo(owner="python", repo="cpython")) # Deferred[Repo]
Installation
Pick the extra matching the HTTP backend you want to run on (the stdlib
urllib and thread-pool backends of action0-client need no extra):
uv add "action0-github-api[httpx]" # or [requests], [aiohttp], [urllib3], [twisted], [all]
Usage
from action0.client.backends.requests import RequestsBackend
from action0.github import CompareCommits, CreateIssue, CreateOrUpdateFile
from action0.github import DownloadReleaseAsset, GetCombinedStatus, GetLatestRelease
from action0.github import GetRateLimit, GetReadme, GetRepo, GetUser, GitHubClient, IssueState
from action0.github import IssueStateReason, ListCommits, ListOrgRepos, ListPulls, MergeMethod
from action0.github import MergePull, PullStateFilter, RepoSearchSort, RepoSort, SearchRepos
from action0.github import UpdateIssue, all_items
with RequestsBackend() as backend:
client = GitHubClient(backend) # token="ghp_..." for higher rate limits
repo = client.send(GetRepo(owner="python", repo="cpython"))
print(repo.full_name, repo.language, repo.stargazers_count)
repos = client.send(ListOrgRepos(org="python", sort=RepoSort.PUSHED, per_page=10))
print([r.name for r in repos]) # one Page[Repo]; repos.next is the next page's operation
for repo in all_items(client, ListOrgRepos(org="python")): # or follow all pages lazily
print(repo.full_name)
pulls = client.send(ListPulls(owner="python", repo="peps", state=PullStateFilter.OPEN))
print([p.title for p in pulls if not p.draft]) # p.head/p.base name the branches
commits = client.send(ListCommits(owner="python", repo="peps", file_path="pep-0008.txt"))
print([c.message.splitlines()[0] for c in commits]) # newest first
diff = client.send(CompareCommits(owner="python", repo="peps", base="main", head="topic"))
print(diff.status, diff.ahead_by, [f.filename for f in diff.files])
readme = client.send(GetReadme(owner="python", repo="peps"))
print(readme.text[:40]) # contents arrive base64-encoded — .text decodes
user = client.send(GetUser(username="gvanrossum"))
print(user.name, user.followers)
hits = client.send(SearchRepos(q="http client language:python", sort=RepoSearchSort.STARS))
print(hits.total_count, [r.full_name for r in hits])
limits = client.send(GetRateLimit()) # this call doesn't count against any limit
print(limits.core.remaining, limits.search.remaining)
Writing works the same way — the typed fields become the JSON body
(needs a token). Updates are PATCH semantics: None fields are
omitted and stay untouched:
issue = client.send(CreateIssue(owner="octo", repo="demo", title="Found a bug", labels=["bug"]))
issue = client.send(
UpdateIssue(
owner="octo",
repo="demo",
issue_number=issue.number,
state=IssueState.CLOSED,
state_reason=IssueStateReason.COMPLETED,
)
)
status = client.send(GetCombinedStatus(owner="octo", repo="demo", ref="main"))
result = client.send( # merge once the statuses and checks are green
MergePull(owner="octo", repo="demo", pull_number=42, merge_method=MergeMethod.SQUASH)
)
written = client.send( # committing a file is one call — raw bytes in, base64 on the wire
CreateOrUpdateFile(
owner="octo", repo="demo", file_path="docs/note.md", message="Add note", content=b"# Hi\n"
)
)
Release assets download as a stream — on a stream=True backend the
body is never held in memory (GitHub 302-redirects to its CDN, so the
backend must follow redirects; most do by default):
with RequestsBackend(stream=True) as backend: # a second backend, just for downloads
release = client.send(GetLatestRelease(owner="octo", repo="demo"))
asset = release.assets[0]
producer = GitHubClient(backend).send(
DownloadReleaseAsset(owner="octo", repo="demo", asset_id=asset.id)
)
with open(asset.name, "wb") as file:
for chunk in producer.chunks():
file.write(chunk)
Uploading works too — UploadReleaseAsset streams a file to a release,
sent through a client pointed at GITHUB_UPLOADS_URL (GitHub takes
uploads on a separate host).
Rate limits are handled by wrapping the backend with action0-client's
retrying wrapper and the GitHub-tuned policy — and saved in the first
place by the conditional-requests hook, which revalidates via ETag
(GitHub's 304 answers don't count against the rate limit):
backend = RetryingSyncBackend(
RequestsBackend(hooks=[ConditionalRequestsHook()]),
GitHubRetryPolicy(),
)
client = GitHubClient(backend, token="ghp_...")
See the quickstart
for asyncio/Twisted usage, authentication, GitHub Enterprise Server and
testing without network — and examples/get_repo.py for a complete,
runnable example.
Status
The core resource areas are covered: repositories (contents incl. file writes, branches, tags, topics, languages, contributors, collaborators), issues (comments, labels, milestones, assignees — each fully manageable), pull requests (merging, reviews incl. line comments, review requests), commits (incl. statuses and check runs), releases (CRUD, generated notes, streaming asset down- and uploads), users, organizations, search and rate limits. Documentation: https://laughinjar.github.io/action0-github-api/
AI disclosure
This library is developed with heavy use of AI coding tools: the code, tests, and documentation are largely written by Claude Code, working from the author's design brief and reviewed by the author. If that changes how much you want to rely on this package, that's a fair call — read the source, it's small.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file action0_github_api-0.1.0.tar.gz.
File metadata
- Download URL: action0_github_api-0.1.0.tar.gz
- Upload date:
- Size: 236.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02e1aa2b01ba9aa95cf3db45af178483916407505b3a3039c8b8b477b3700fb3
|
|
| MD5 |
32567ce6b8df6878cc4f851b0095904a
|
|
| BLAKE2b-256 |
247cba75102dbb43c3d855119c10a4ba6f4b206ed8ce73a85e12f505d292c5c5
|
Provenance
The following attestation bundles were made for action0_github_api-0.1.0.tar.gz:
Publisher:
release.yml on LaughInJar/action0-github-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
action0_github_api-0.1.0.tar.gz -
Subject digest:
02e1aa2b01ba9aa95cf3db45af178483916407505b3a3039c8b8b477b3700fb3 - Sigstore transparency entry: 2475580101
- Sigstore integration time:
-
Permalink:
LaughInJar/action0-github-api@59810e1185b54930c164a3b77438c0279929c720 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/LaughInJar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59810e1185b54930c164a3b77438c0279929c720 -
Trigger Event:
push
-
Statement type:
File details
Details for the file action0_github_api-0.1.0-py3-none-any.whl.
File metadata
- Download URL: action0_github_api-0.1.0-py3-none-any.whl
- Upload date:
- Size: 74.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2eba5c5aad3841705366590ca30f854bb4a45df711823897664e4e6f3e61663a
|
|
| MD5 |
b85981bac429f337a548a4247e584577
|
|
| BLAKE2b-256 |
8cdd8229932782b31e668d9d5eaab607ca98f2c6b12f9ec525a8313dc61f5b3c
|
Provenance
The following attestation bundles were made for action0_github_api-0.1.0-py3-none-any.whl:
Publisher:
release.yml on LaughInJar/action0-github-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
action0_github_api-0.1.0-py3-none-any.whl -
Subject digest:
2eba5c5aad3841705366590ca30f854bb4a45df711823897664e4e6f3e61663a - Sigstore transparency entry: 2475580131
- Sigstore integration time:
-
Permalink:
LaughInJar/action0-github-api@59810e1185b54930c164a3b77438c0279929c720 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/LaughInJar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59810e1185b54930c164a3b77438c0279929c720 -
Trigger Event:
push
-
Statement type: