GitHub Client
GitHubClient is a bundled HakiAPI client for the GitHub REST and GraphQL APIs, built on top of BaseAPIClient. It inherits retries, typed exceptions, and auth handling automatically, and adds GitHub-specific helpers for pagination, language aggregation, activity, README checks, and GraphQL contribution data.
Initialization
from hakiapi import GitHubClient # Token is optional — public endpoints work without one, # but you'll hit GitHub's much lower unauthenticated rate limit. client = GitHubClient(token="ghp_your_secret_token")
Under the hood, the client is preconfigured for GitHub's API:
base_urlis set tohttps://api.github.com.- If a
tokenis passed, it's wrapped inBearerTokenAuthand attached automatically. - The session sends
Accept: application/vnd.github+json,X-GitHub-Api-Version: 2022-11-28, and aUser-Agentheader on every request.
Like other BaseAPIClient subclasses, it supports the context manager protocol:
with GitHubClient(token="ghp_...") as gh: gh.get_user("torvalds")
Profiles & Repositories
gh.get_user("torvalds") # fetch a user's profile gh.search_users("location:hyderabad") # single page of matching users gh.get_all_search_users("python") # auto-paginated generator over search results gh.get_user_repos("torvalds") # single page of public repos gh.get_all_user_repos("torvalds") # auto-paginated generator over all public repos gh.get_repo_languages("torvalds", "linux") # byte breakdown of languages in one repo gh.get_aggregate_user_languages("torvalds") # sums language bytes across every repo
get_all_user_repos(username, **kwargs)paginates automatically viapaginate(), so it yields every repo lazily instead of one page at a time.get_aggregate_user_languages(username, **kwargs)walks every repo fromget_all_user_repos(), callsget_repo_languages()on each, and sums byte counts per language into a singledict[str, int]. If a language lookup for a given repo raisesHakiAPIError(e.g. an empty or inaccessible repo), that repo is silently skipped rather than failing the whole aggregation.
Search & Activity
gh.get_user_authored_activity("torvalds")
get_user_authored_activity(username, **kwargs) runs two GitHub code-search queries (author:{username} type:pr and author:{username} type:issue) against search/issues, each capped at 5 results, and returns:
{ "pull_requests": {"total_count": ..., "recent_items": [...]}, "issues": {"total_count": ..., "recent_items": [...]}, }
README Checks
gh.check_readme_exists("torvalds", "linux") # -> bool gh.check_top_repos_readmes("torvalds", repos, top_n=5) # -> dict[str, bool]
check_readme_exists(owner, repo_name, **kwargs)sends aHEADrequest to GitHub's canonicalrepos/{owner}/{repo}/readmeendpoint (5s timeout by default) and returnsTrueon200,Falseon404or any other error — it never raises.check_top_repos_readmes(owner, repos, top_n=5, **kwargs)takes a list of repo dicts (as returned byget_user_repos()), filters out forks, sorts the rest bystargazers_countdescending, keeps the toptop_n, and checks each withcheck_readme_exists(). Returns{repo_name: has_readme}— only for the repos it checked.
GraphQL
GitHubClient also ships a GraphQL execution layer on top of the same BaseAPIClient infrastructure, so GraphQL calls get the same retries, timeout handling, and auth as REST calls.
with GitHubClient(token="ghp_...") as gh: data = gh.execute_graphql( """ query($login: String!) { user(login: $login) { name bio } } """, variables={"login": "torvalds"}, ) print(data["user"]["name"])
execute_graphql(query, variables=None, **kwargs)— the low-level engine.POSTs{"query": ..., "variables": ...}to/graphql. GraphQL notoriously returns HTTP200 OKeven when the query itself failed, with the real error buried in the response body —execute_graphqlchecks for an"errors"key and raisesHakiAPIErrorjoining every message it finds, instead of letting a broken query silently returnNone. On success it returns just the"data"portion of the payload (not the full envelope), so you access fields directly (data["user"]["name"], notdata["data"]["user"]["name"]).get_user_contributions(username, from_date=None, to_date=None, **kwargs)— a ready-made query built onexecute_graphql. Fetches a 365-day contribution calendar (with a weekly breakdown) plus the user's 5 most recent pull requests and issues.from_date/to_datemust be ISO 8601 strings (e.g."2025-01-01T00:00:00Z") and are optional. Returns:
{ "recent_contributions_365_days": { "total_contributions": ..., "weeks": [...], }, "lifetime_activity": { "pull_requests": {"total_count": ..., "recent_items": [...]}, "issues": {"total_count": ..., "recent_items": [...]}, }, }
Full Profile Aggregation
profile = gh.fetch_full_profile_data("torvalds")
fetch_full_profile_data(username, **kwargs) combines everything above into one call:
- Fetches contribution data via
get_user_contributions(). - Fetches up to 100 recently-updated repos via
get_user_repos(). - Builds a
language_breakdown— a count of repos per primarylanguage(not byte totals; useget_aggregate_user_languages()for that). - Checks README presence for the top 5 owned repos via
check_top_repos_readmes(), and attacheshas_readme(True/False/Noneif outside the top 5) onto each repo object.
Returns:
{ "username": ..., "repositories": { "items": [...], # each repo now has a "has_readme" key "language_breakdown": {...}, }, "recent_contributions_365_days": {...}, "lifetime_activity": {...}, "readme_statuses": {...}, }
Errors
Every method here inherits BaseAPIClient's exception handling — RateLimitError, AuthenticationError, ClientError, ServerError, and RequestTimeoutError all subclass HakiAPIError:
from hakiapi.core.exceptions import RateLimitError, HakiAPIError try: gh.get_user("torvalds") except RateLimitError as e: print(f"Rate limited — retry after {e.retry_after}s") except HakiAPIError as e: print(f"GitHub API error: {e}")