🚀 HakiAPI
Build production-grade Python API SDKs — not boilerplate.
Authentication · OAuth 2.0 · Retries · Circuit Breaker · Pagination · Typed Exceptions · Sync + Async
Stop rewriting authentication, retries, and pagination for every API client you build.
Installation • Quick Start • Features • Core Concepts • Circuit Breaker • Async Client • Bundled Clients • Create Your Own Client • Architecture • Roadmap
Why HakiAPI?
Every API client grows the same infrastructure, in the same order. You start with a simple HTTP call. Then you add authentication. Then retries. Then pagination. Then timeout and exception handling. A month later, you've rebuilt the same plumbing you already wrote for the last five projects.
HakiAPI extracts all of that into one reusable core (BaseAPIClient), so every client you build on top of it inherits the same battle-tested behavior automatically. Instead of writing infrastructure, you write endpoint logic.
Raw requests vs. HakiAPI
Raw requests |
HakiAPI | |
|---|---|---|
| OAuth 2.0 Flow | Hand-roll the consent URL, spin up a redirect server, parse the callback yourself | GoogleOAuthFlow builds the consent URL, opens the browser, catches the redirect on localhost, verifies CSRF state, and exchanges the code for you |
| Token Persistence | Read/write a JSON file yourself and hope nothing corrupts it mid-write | FileTokenStore writes atomically (temp file + os.replace) with 0600 permissions |
| Retry Logic | Wire up your own urllib3.Retry + HTTPAdapter |
Built into every BaseAPIClient session with exponential backoff on 429/500/502/503/504 |
| Static Auth | Reimplement Bearer/HMAC/API-key headers per project | 5 reusable AuthBase strategies, drop-in |
| Error Handling | Manually branch on response.status_code everywhere |
Raised as a typed, catchable exception hierarchy carrying status_code and the original response |
| Pagination | Write a custom while loop per API's pagination style |
paginate() auto-detects Link-header, data/meta.next_token, messages/nextPageToken, and items/nextPageToken styles, yielding lazily |
| Async I/O | Swap in aiohttp/httpx yourself and reimplement retries, timeouts, and status handling on top of it |
AsyncBaseAPIClient is an httpx-backed mirror of BaseAPIClient — same retry engine, same typed exceptions, async/await throughout |
| Cascading Failures | A struggling downstream service keeps getting hammered by retries until your own app falls over with it | CircuitBreaker decorator trips OPEN after N consecutive failures, fails fast with CircuitOpenError during cooldown, then auto-probes recovery with a single trial call |
✨ Features
| Feature | Details |
|---|---|
| 🔐 Interactive OAuth 2.0 Flow | GoogleOAuthFlow drives Google's Authorization Code flow end-to-end: builds the consent URL, opens the system browser, boots a one-shot local HTTPServer to catch the redirect, validates the CSRF state token, and exchanges the code for tokens. |
| 🔁 Manual Token Refresh | refresh_access_token() exchanges a stored refresh_token for a new access_token without user interaction, and wipes the token store automatically if Google reports the grant as revoked. |
| 🗄️ Atomic Token Vault | FileTokenStore persists tokens to a local JSON file by writing to a temp file and swapping it in with os.replace(), so a crash mid-write can never leave a corrupted token file. The file is chmod 0600. |
| 🔐 Multiple Auth Strategies | BearerTokenAuth, HeaderApiKeyAuth, QueryApiKeyAuth, HmacAuth (SHA-256 request signing), and OAuth2Auth for wiring a GoogleOAuthFlow directly into a requests.Session. |
| 🔁 Automatic Retries | create_retry_adapter() mounts an HTTPAdapter with exponential backoff on 429/500/502/503/504 onto every BaseAPIClient session, deferring status handling to HakiAPI's own exceptions via raise_on_status=False. |
| 📄 Smart Pagination | paginate() auto-detects GitHub-style Link headers, Twitter-style meta.next_token, Gmail-style messages + nextPageToken, and Calendar-style items + nextPageToken — all as one lazy generator. |
| ⚠️ Typed Exceptions | RateLimitError (with retry_after), AuthenticationError (401/403), ClientError (4xx), ServerError (5xx), RequestTimeoutError — all inherit from HakiAPIError, which carries status_code and the original response. |
| ⚡ Async Client | AsyncBaseAPIClient is a fully async/await, httpx-powered counterpart to BaseAPIClient — same exponential backoff on 429/500/502/503/504, the same typed exception hierarchy, Retry-After clamping, SSRF-safe URL/endpoint validation, and a max-response-size guard, so you don't lose any safety behavior by going async. |
| 🧯 Circuit Breaker | Thread-safe CircuitBreaker decorator implementing the classic CLOSED → OPEN → HALF_OPEN state machine. Fails fast with CircuitOpenError (carrying a retry_after cooldown) once failure_threshold consecutive failures are hit, then automatically allows a single trial request through after recovery_timeout seconds to probe for recovery. |
| 📦 Ready-to-use Clients | GitHubClient (REST + GraphQL), GmailClient, GoogleCalendarClient — out of the box. |
Installation
pip install hakiapi
Requires Python 3.10+. Core dependencies are requests>=2.32.0 and urllib3>=1.26.0.
AsyncBaseAPIClient is built on httpx, which isn't installed by default — add it if you want the async client:
pip install httpx
Quick Start
1. Interactive OAuth 2.0 (Google Calendar)
GoogleOAuthFlow.get_token() checks the TokenStore first. If a valid, non-expired token is already saved, it's returned immediately. Otherwise it opens your browser, runs the full consent flow, and persists the result:
import os
from dotenv import load_dotenv
from hakiapi.clients.google_calendar import GoogleCalendarClient
from hakiapi.core.oauth.google import GoogleOAuthFlow
from hakiapi.core.oauth.token_store import FileTokenStore
# Load variables from your .env file into os.environ
load_dotenv()
# 1. Set up the flow and the token vault
oauth_flow = GoogleOAuthFlow(
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
scopes=["https://www.googleapis.com/auth/calendar.readonly"],
store=FileTokenStore("my_secure_token.json"),
redirect_port=8765, # must match an authorized redirect URI in Google Cloud Console
)
# 2. get_token() returns the cached token if it's still valid,
# otherwise it opens the browser and runs the full consent flow.
token = oauth_flow.get_token()
# 3. Initialize your client with the raw access token
with GoogleCalendarClient(token=token.access_token) as calendar:
for event in calendar.events.upcoming(max_results=3):
print(event.get("summary"))
Note:
get_token()does not silently refresh an expired token — it re-runs the interactive consent flow when the stored token is missing or expired. If you want silent, non-interactive refreshes using a savedrefresh_token, callrefresh_access_token()fromhakiapi.core.oauth.refreshexplicitly (see OAuth 2.0 below).
2. Automatic Pagination (GitHub)
Forget page numbers, while loops, and manually checking for a next page. paginate() follows Link headers automatically and yields lazily:
from hakiapi.clients.github import GitHubClient
with GitHubClient() as github:
# Lazily walks every page of the user's public repos
for repo in github.get_all_user_repos("torvalds"):
print(repo["name"])
3. Async Requests (httpx-based)
AsyncBaseAPIClient mirrors BaseAPIClient method-for-method, just with async/await:
import asyncio
from hakiapi.core.async_base_client import AsyncBaseAPIClient
async def main():
async with AsyncBaseAPIClient(base_url="https://api.github.com") as client:
user = await client.get("/users/torvalds")
print(user["name"])
asyncio.run(main())
Core Concepts
Exception Handling
Every exception raised by BaseAPIClient._request() inherits from HakiAPIError, carrying status_code and the original response object:
from hakiapi.core.exceptions import AuthenticationError, RateLimitError, ServerError
try:
github.get_user("torvalds")
except RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except AuthenticationError:
print("Invalid credentials.")
except ServerError:
print("GitHub is currently unavailable.")
| Exception | Raised when | Extra attributes |
|---|---|---|
RateLimitError |
HTTP 429 |
retry_after — parsed from the Retry-After header, if present |
AuthenticationError |
HTTP 401 / 403 |
auth_method |
ClientError |
Any other 4xx |
— |
ServerError |
Any 5xx |
— |
RequestTimeoutError |
The request times out at the network level (no HTTP response was ever received) | timeout_duration |
Authentication Strategies (core/auth.py)
All strategies implement requests.auth.AuthBase, so they drop straight into BaseAPIClient(auth=...):
from hakiapi.core.auth import BearerTokenAuth, HeaderApiKeyAuth, QueryApiKeyAuth, HmacAuth
BearerTokenAuth(token)— setsAuthorization: Bearer <token>.HeaderApiKeyAuth(header_name, api_key)— injects the key under a custom header.QueryApiKeyAuth(param_name, api_key)— appends the key as a query parameter, preserving any existing query string.HmacAuth(api_key, secret_key, ...)— signs each request with HMAC-SHA256 overMETHOD\nPATH\nTIMESTAMP\nBODY(newline-delimited to prevent field-collision signature forgery), sending the key, timestamp, and signature as headers. RaisesTypeErrorfor streaming bodies, which aren't supported.OAuth2Auth(flow)— wraps any object exposingget_token()(likeGoogleOAuthFlow) and injects a freshAuthorization: Bearerheader on every request.
Retry Engine (core/retry.py)
create_retry_adapter() builds an HTTPAdapter backed by urllib3.util.Retry:
- 3 retries by default, with an exponential
backoff_factorof1.0. - Retries on
429, 500, 502, 503, 504by default (configurable viastatus_forcelist). raise_on_status=False—urllib3never raises on its own; HakiAPI's typed exceptions handle the final failure.- Mounted on both
http://andhttps://for everyBaseAPIClientsession automatically.
Circuit Breaker (core/circuit_breaker.py)
CircuitBreaker protects downstream services from cascading failures and stops a struggling API from being hammered by an ever-growing pile of retries. It's a thread-safe decorator you can drop onto any synchronous callable:
from hakiapi.core.circuit_breaker import CircuitBreaker, CircuitOpenError
from hakiapi.core.exceptions import HakiAPIError
breaker = CircuitBreaker(
failure_threshold=5, # consecutive failures before the circuit opens
recovery_timeout=30.0, # seconds to wait before allowing a trial request
expected_exceptions=(HakiAPIError,), # exception types that count as failures
)
@breaker
def get_user(user_id: str):
return github.get_user(user_id)
try:
get_user("torvalds")
except CircuitOpenError as e:
print(f"Downstream is unhealthy — retry in {e.retry_after:.1f}s")
It implements the classic three-state machine:
| State | Behavior |
|---|---|
CLOSED |
Normal operation. Requests pass through; a successful call resets the failure counter. |
OPEN |
Failing fast. Every call is blocked instantly with CircuitOpenError (no call is made to the wrapped function) until recovery_timeout seconds have elapsed. |
HALF_OPEN |
Testing recovery. Once the timeout elapses, a single trial request is allowed through — success closes the circuit and resets the counter, failure reopens it immediately and restarts the cooldown. |
- Thread-safe. All state transitions and counters are guarded by a single
threading.Lock, so oneCircuitBreakerinstance can safely protect a callable shared across threads. - Lazy state evaluation. The
OPEN → HALF_OPENtransition happens the momentstateis read (or a wrapped call is made) afterrecovery_timeouthas elapsed — there's no background timer or polling thread. CircuitOpenErrorinherits fromHakiAPIErrorand carriesretry_after(seconds remaining in the cooldown, clamped to>= 0), so callers can back off intelligently instead of guessing.- Configurable failure scope.
expected_exceptionscontrols which exception types trip the breaker — defaults toHakiAPIError, so unrelated exceptions (e.g. aValueErrorfrom bad input) pass straight through without affecting circuit state. - Self-clamping configuration.
failure_thresholdis floored at1andrecovery_timeoutat0.1seconds, so a misconfigured0or negative value can't accidentally create a circuit that never opens or reopens instantly forever.
Note:
CircuitBreakeris currently a standalone utility — import it directly fromhakiapi.core.circuit_breakerand wrap it around any client method (syncBaseAPIClientcalls, a bundled client's methods, or your own functions) where you want fail-fast protection against a struggling dependency.
Async Client (core/async_base_client.py)
AsyncBaseAPIClient is a ground-up, httpx-backed rewrite of BaseAPIClient for asyncio codebases — it is not a thin wrapper around the sync client. It re-implements the same guarantees natively on top of httpx.AsyncClient instead of requests:
from hakiapi.core.async_base_client import AsyncBaseAPIClient
async with AsyncBaseAPIClient(
base_url="https://api.example.com",
timeout=10.0,
max_retries=3,
backoff_factor=0.5,
max_response_bytes=10 * 1024 * 1024,
) as client:
data = await client.get("/resource")
created = await client.post("/resource", json={"name": "haki"})
- Same retry engine, no
HTTPAdapter. Becausehttpxdoesn't support mounting aurllib3-style adapter, retries are implemented directly in_request(): exponential backoff (backoff_factor * 2**attemptplus jitter) on429/500/502/503/504, and onhttpx.TimeoutException/httpx.RequestErrorfor network-level failures — up tomax_retriesattempts. Retry-After-aware. A429with a validRetry-Afterheader sleeps for exactly that long (clamped to 300s) instead of the backoff curve.- Same typed exception hierarchy.
RateLimitError,AuthenticationError,ClientError,ServerError, andRequestTimeoutErrorare raised from the exact samehakiapi.core.exceptionsmodule the sync client uses — oneexceptblock works for both. - Same security posture.
base_urlis validated to behttp(s)with a real host at construction time; everyendpointpassed toget/post/etc. is rejected if it's absolute or protocol-relative (blocks SSRF via a malicious or templated endpoint string); redirects are not followed automatically (follow_redirects=False). - Response-size guard. Responses larger than
max_response_bytesraiseHakiAPIErrorbefore the body is handed back to you, checked againstContent-Lengthwhen present. - Async context manager.
async with AsyncBaseAPIClient(...) as client:callsclient.close()(which awaitshttpx.AsyncClient.aclose()) on exit;close()is idempotent, so calling it more than once is safe. get,post,put,delete,patchallawaitthrough the same_request()pipeline as the sync client, and all acceptraw_response=Trueto get thehttpx.Responseback instead of the parsed body.
Note:
AsyncBaseAPIClientcurrently lives inhakiapi.core.async_base_clientas a standalone import — it isn't re-exported from the top-levelhakiapipackage yet, so import it directly as shown above.
Smart Pagination (core/paginator.py)
paginate(client, endpoint, max_pages=None, **kwargs) is a generator that keeps requesting pages until it runs out, detecting the item list and the "next page" signal from the response shape:
| Response shape | Items key | Next-page signal |
|---|---|---|
| Raw JSON list | the list itself | Link response header (rel="next") |
{"data": [...], "meta": {...}} (Twitter/X-style) |
data |
meta.next_token |
{"messages": [...], "nextPageToken": ...} (Gmail-style) |
messages |
nextPageToken |
{"items": [...], "nextPageToken": ...} (Calendar-style) |
items |
nextPageToken |
{"resultSizeEstimate": 0} (Gmail empty result) |
— | stops cleanly, no error |
Any other shape raises ValueError("Unexpected pagination response: ..."). Pass max_pages to cap how many pages are fetched.
OAuth 2.0 (core/oauth/)
The OAuth engine is split into three independent pieces:
token_store.py—OAuthToken(a dataclass withaccess_token,refresh_token,expires_at,scopes, and anis_expiredproperty with a 30-second leeway buffer) and theTokenStoreabstract base class.FileTokenStoreis the concrete implementation: it serializes tokens to JSON, writes atomically via a temp file +os.replace(), and sets0600permissions on the file.google.py—GoogleOAuthFlowdrives the full interactive Authorization Code flow: builds the consent URL (access_type=offline,prompt=consentto force a refresh token on every run), opens it withwebbrowser.open(), boots a one-shothttp.server.HTTPServeronlocalhost:<redirect_port>to catch the redirect, validates the CSRFstateparameter, and exchanges the authorization code for tokens via a direct POST to Google's token endpoint. RaisesOAuthFlowErroron denial, timeout, astatemismatch, or a failed exchange.refresh.py—refresh_access_token(token, client_id, client_secret, store)is a standalone function that exchanges a savedrefresh_tokenfor a newaccess_tokenwithout opening a browser. If Google rejects the refresh (revoked/invalid grant), it callsstore.delete_token()so the nextget_token()call cleanly falls back to the interactive flow.
These three pieces are intentionally decoupled — GoogleOAuthFlow.get_token() only checks expiry and re-runs the interactive flow if needed; wiring in silent refreshes via refresh_access_token() is left to the caller (or to OAuth2Auth, once you build that logic into your own flow object).
Bundled Clients
GitHubClient — REST + GraphQL
from hakiapi.clients.github import GitHubClient
with GitHubClient(token="ghp_...") as gh: # token is optional for public endpoints
gh.get_user("torvalds")
gh.search_users("location:hyderabad")
gh.get_all_search_users("python") # auto-paginated generator
gh.get_user_repos("torvalds") # single page
gh.get_all_user_repos("torvalds") # auto-paginated generator
gh.get_repo_languages("torvalds", "linux")
gh.get_aggregate_user_languages("torvalds") # sums languages across every repo
gh.get_user_authored_activity("torvalds") # recent authored PRs + issues
gh.execute_graphql(query, variables={...}) # raises HakiAPIError on GraphQL-level errors
gh.get_user_contributions("torvalds", from_date="2025-01-01T00:00:00Z")
get_aggregate_user_languages() walks every repository returned by get_all_user_repos() and silently skips any repo whose language lookup raises HakiAPIError, so one broken/empty repo doesn't fail the whole aggregation.
GraphQL Engine
GitHubClient isn't purely REST — it 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 everything else.
from hakiapi.clients.github import GitHubClient
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. ItPOSTs{"query": ..., "variables": ...}to GitHub's/graphqlendpoint and unwraps the response. GraphQL is notorious for returning HTTP200 OKeven when the query itself failed, with the real error buried in the response body —execute_graphqlchecks for an"errors"key in the payload and raises aHakiAPIErrorjoining every message it finds, instead of letting a broken query silently returnNone. On success it returns just the"data"portion of the payload.get_user_contributions(username, from_date=None, to_date=None, **kwargs)— a ready-made query built on top ofexecute_graphql. It fetches a user'scontributionsCollection: total contributions, commit contributions, issue contributions, and pull-request contributions, optionally scoped to a date range.from_date/to_datemust be ISO 8601 strings (e.g."2025-01-01T00:00:00Z").
GmailClient — resource-based routing
from hakiapi import GmailClient
with GmailClient(token=access_token) as gmail:
gmail.profile.get() # users/{id}/profile
gmail.labels.list() # users/{id}/labels
gmail.messages.get(message_id) # a single message
gmail.messages.list(max_pages=2) # auto-paginated generator
gmail.messages.search("is:unread") # auto-paginated generator with a query
gmail.messages.send({"raw": base64_rfc2822_string})
GoogleCalendarClient — resource-based routing
from hakiapi import GoogleCalendarClient
with GoogleCalendarClient(token=access_token) as cal:
cal.calendars.list(max_pages=1)
cal.events.get(event_id)
cal.events.list(calendar_id="primary")
cal.events.today() # midnight-to-midnight UTC, recurring events expanded
cal.events.upcoming(max_results=5) # next N events from now, single page
cal.events.create({"summary": "...", "start": {...}, "end": {...}})
cal.events.delete(event_id)
today() and upcoming() both auto-fill timeMin/timeMax, set singleEvents=True to expand recurring events, and sort by startTime — you only pass the calendar ID.
Create Your Own Client
Subclass BaseAPIClient, point it at a base URL, and define your endpoints as plain methods. Authentication, retries, timeout handling, and typed exceptions are inherited automatically:
from hakiapi import BaseAPIClient
class WeatherClient(BaseAPIClient):
def __init__(self, **kwargs):
super().__init__(base_url="https://api.open-meteo.com/v1", **kwargs)
def get_weather(self, latitude: float, longitude: float):
return self.get(
"forecast",
params={
"latitude": latitude,
"longitude": longitude,
"current_weather": True,
},
)
if __name__ == "__main__":
# Hyderabad, Telangana, India
with WeatherClient() as client:
weather = client.get_weather(latitude=17.385, longitude=78.4867)
print(weather["current_weather"])
BaseAPIClient exposes get, post, put, patch, and delete, all routed through _request(), which handles the retry-mounted session, timeout errors, status-code-to-exception mapping, and JSON/text response parsing (falling back to response.text if the body isn't valid JSON). Pass raw_response=True to get the raw requests.Response instead — this is what paginate() uses internally to read the Link header.
Architecture & Project Structure
hakiapi/
├── core/
│ ├── oauth/
│ │ ├── google.py # GoogleOAuthFlow — interactive Authorization Code flow
│ │ ├── refresh.py # refresh_access_token() — silent refresh via refresh_token
│ │ └── token_store.py # OAuthToken, TokenStore (ABC), FileTokenStore
│ ├── auth.py # BearerTokenAuth, HeaderApiKeyAuth, QueryApiKeyAuth, HmacAuth, OAuth2Auth
│ ├── retry.py # create_retry_adapter() — exponential-backoff HTTPAdapter factory
│ ├── circuit_breaker.py # CircuitBreaker — CLOSED/OPEN/HALF_OPEN fail-fast decorator
│ ├── paginator.py # paginate() — Link-header + token-based pagination
│ ├── base_client.py # BaseAPIClient — session, retries, exception mapping
│ ├── async_base_client.py # AsyncBaseAPIClient — httpx-based async counterpart
│ └── exceptions.py # HakiAPIError hierarchy
│
└── clients/
├── github.py # GitHubClient — REST + GraphQL
├── gmail.py # GmailClient — profile / labels / messages resources
└── google_calendar.py # GoogleCalendarClient — calendars / events resources
Design Principles
- Infrastructure should be written once.
- API clients should remain lightweight.
- Explicit is better than magical.
- Strong typing improves maintainability.
- Production readiness should be the default, not an afterthought.
- Developer experience matters as much as correctness.
Testing
pip install hakiapi[dev]
pytest
- ✅ 340+ tests passing
- ✅ Core framework covered:
auth,retry,circuit_breaker,paginator,base_client,async_base_client,exceptions - ✅
AsyncBaseAPIClientcovered end-to-end viahttpx.MockTransport— success paths, retry/backoff,Retry-Afterhandling, timeouts, SSRF/endpoint validation, response-size limits, and context-manager lifecycle - ✅
CircuitBreakercovered end-to-end — state transitions (CLOSED → OPEN → HALF_OPEN), threshold clamping,retry_aftercalculation, success-resets-counter behavior, and unexpected-exception passthrough, withtime.monotonicmocked for deterministic timing - ✅ Full OAuth 2.0 engine covered:
google.py(interactive flow) andrefresh.py(silent refresh), fully mocked - ✅
FileTokenStoreatomic-write behavior covered - ✅
GitHubClient,GmailClient,GoogleCalendarClientcovered
Roadmap
Completed
- Base API framework (
BaseAPIClient) - Authentication strategies (Bearer, Header API Key, Query API Key, HMAC, OAuth2)
- Retry engine with exponential backoff
- Automatic pagination (Link header,
data/meta,messages/items+ token styles) - Typed exception hierarchy
- Interactive Google OAuth 2.0 flow (local redirect interceptor, CSRF-protected)
- Atomic
FileTokenStoreand standalone silent-refresh routine -
GitHubClient,GmailClient,GoogleCalendarClient - Async client (
AsyncBaseAPIClient,httpx-based) - Circuit breaker (
CircuitBreaker) for fail-fast protection against cascading failures
Planned
- Stripe client
- Twitter/X client
- Wire automatic silent refresh into
GoogleOAuthFlow.get_token() - Async versions of
GitHubClient/GmailClient/GoogleCalendarClienton top ofAsyncBaseAPIClient - Top-level
hakiapiexport forAsyncBaseAPIClient - Plugin system
Contributing
Contributions are welcome — bug fixes, documentation, tests, or new clients. Please open an issue before proposing major changes so we can discuss the approach first.
License
MIT License — see LICENSE for details.
⭐ If HakiAPI saved you from rewriting the same API client for the tenth time, consider giving it a star.
It helps more developers discover the project and motivates future development.
Built with ❤️ by Gugilla Aakash
Metadata
Release files for hakiapi 2.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hakiapi-2.1.4.tar.gz | 62.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hakiapi-2.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.2 kB
Release files / hakiapi-2.1.4.tar.gz
| Download URL | hakiapi-2.1.4.tar.gz |
|---|---|
| Size | 62.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e0c72789b66d294f99646074007c8cd49a4681aa7efe4b93c29163741a6ac996
|
|
BLAKE2b-256 checksum How to use checksums |
aa33c958b79797172400eef46c1baeced16ac83094235cf5f907bc1ff9fc3d28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / hakiapi-2.1.4-py3-none-any.whl
| Download URL | hakiapi-2.1.4-py3-none-any.whl |
|---|---|
| Size | 35.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5bfdd47db129e2bf3790d0910cc60941fbeb50f6697974411b8a8e243acbbd8
|
|
BLAKE2b-256 checksum How to use checksums |
bc32a56e28fdaabd97cacfc6367454cac23d29966a0462e20fa6f7b632d09cc6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency log