hakiapi 2.0.0__tar.gz → 2.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. hakiapi-2.0.2/PKG-INFO +300 -0
  2. hakiapi-2.0.2/README.md +273 -0
  3. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/clients/github.py +33 -4
  4. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/paginator.py +8 -4
  5. hakiapi-2.0.2/hakiapi.egg-info/PKG-INFO +300 -0
  6. {hakiapi-2.0.0 → hakiapi-2.0.2}/pyproject.toml +1 -1
  7. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_github.py +73 -10
  8. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_paginator.py +1 -1
  9. hakiapi-2.0.0/PKG-INFO +0 -428
  10. hakiapi-2.0.0/README.md +0 -401
  11. hakiapi-2.0.0/hakiapi.egg-info/PKG-INFO +0 -428
  12. {hakiapi-2.0.0 → hakiapi-2.0.2}/LICENSE +0 -0
  13. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/__init__.py +0 -0
  14. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/clients/__init__.py +0 -0
  15. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/clients/gmail.py +0 -0
  16. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/clients/google_calendar.py +0 -0
  17. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/__init__.py +0 -0
  18. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/auth.py +0 -0
  19. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/base_client.py +0 -0
  20. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/exceptions.py +0 -0
  21. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/oauth/__init__.py +0 -0
  22. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/oauth/google.py +0 -0
  23. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/oauth/refresh.py +0 -0
  24. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/oauth/token_store.py +0 -0
  25. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi/core/retry.py +0 -0
  26. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi.egg-info/SOURCES.txt +0 -0
  27. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi.egg-info/dependency_links.txt +0 -0
  28. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi.egg-info/requires.txt +0 -0
  29. {hakiapi-2.0.0 → hakiapi-2.0.2}/hakiapi.egg-info/top_level.txt +0 -0
  30. {hakiapi-2.0.0 → hakiapi-2.0.2}/setup.cfg +0 -0
  31. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_auth.py +0 -0
  32. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_base_client.py +0 -0
  33. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_exceptions.py +0 -0
  34. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_gmail.py +0 -0
  35. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_google.py +0 -0
  36. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_google_calender.py +0 -0
  37. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_refresh.py +0 -0
  38. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_retry.py +0 -0
  39. {hakiapi-2.0.0 → hakiapi-2.0.2}/tests/test_token_store.py +0 -0
hakiapi-2.0.2/PKG-INFO ADDED
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: hakiapi
3
+ Version: 2.0.2
4
+ Summary: A modern Python framework for building clean, typed, and extensible API clients.
5
+ Author: Gugilla Aakash
6
+ Author-email: gugillaaakash6@gmail.com
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/Gugilla-Aakash/hakiapi
9
+ Project-URL: Source Code, https://github.com/Gugilla-Aakash/hakiapi
10
+ Project-URL: Bug Tracker, https://github.com/Gugilla-Aakash/hakiapi/issues
11
+ Keywords: api-client,retry,pagination,rest-api,http
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Intended Audience :: Developers
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: requests>=2.32.0
21
+ Requires-Dist: urllib3>=1.26.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest; extra == "dev"
24
+ Requires-Dist: responses; extra == "dev"
25
+ Requires-Dist: ruff; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ <div align="center">
29
+
30
+ # 🚀 HakiAPI
31
+
32
+ ### Build production-grade Python API SDKs — not boilerplate.
33
+
34
+ Authentication · Self-Healing OAuth 2.0 · Retries · Pagination · Typed Exceptions
35
+
36
+ [![PyPI](https://img.shields.io/pypi/v/hakiapi?style=for-the-badge)](https://pypi.org/project/hakiapi/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/hakiapi?style=for-the-badge)](https://pypi.org/project/hakiapi/)
38
+ [![License](https://img.shields.io/github/license/Gugilla-Aakash/hakiapi?style=for-the-badge)](LICENSE)
39
+ [![Tests](https://img.shields.io/badge/tests-280%2B_passing-success?style=for-the-badge)](#testing)
40
+ [![Typing](https://img.shields.io/badge/typing-fully_typed-blue?style=for-the-badge)](#features)
41
+ [![Downloads](https://img.shields.io/pypi/dm/hakiapi?style=for-the-badge)](https://pypistats.org/packages/hakiapi)
42
+
43
+ **Stop rewriting authentication, retries, and pagination for every API client you build.**
44
+
45
+ [Installation](#installation) • [Quick Start](#quick-start) • [Features](#features) • [Architecture](#architecture) • [Create Your Own Client](#create-your-own-client) • [Roadmap](#roadmap)
46
+
47
+ </div>
48
+
49
+ ---
50
+
51
+ ## Why HakiAPI?
52
+
53
+ 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.
54
+
55
+ HakiAPI extracts all of that into one reusable core, so every client you build on top of it inherits enterprise-grade behavior automatically. Instead of writing infrastructure, you write business logic.
56
+
57
+ ### Without HakiAPI vs. with HakiAPI
58
+
59
+ | | Raw `requests` | HakiAPI |
60
+ |---|---|---|
61
+ | **OAuth 2.0 Flow** | Copy-paste URLs, manually spin up servers, hand-roll refreshes | Fully automated local interceptor + atomic token vault + auto-refresh |
62
+ | **Retry Logic** | Write your own `urllib3.Retry` + `HTTPAdapter` wiring | Built into `BaseAPIClient` with exponential backoff on 429/50x |
63
+ | **Static Auth** | Reimplement Bearer/HMAC/API keys per project | 4 reusable `AuthBase` strategies, drop-in |
64
+ | **Error Handling** | Manually check `response.status_code` everywhere | Raised as statically-typed, catchable exceptions |
65
+ | **Pagination** | Write a custom `while` loop per API's pagination style | Auto-detects Link-header & cursor pagination, iterated lazily |
66
+
67
+ ---
68
+
69
+ ## ✨ Features
70
+
71
+ | Feature | Details |
72
+ |---|---|
73
+ | 🔄 **Self-Healing OAuth 2.0** | Interactive local server interceptor, CSRF-protected browser flow, and background token refreshing. |
74
+ | 🗄️ **Atomic Token Vault** | `FileTokenStore` guarantees zero state corruption during concurrent writes using secure temp-file swapping. |
75
+ | 🔐 **Multiple Auth Strategies** | `BearerTokenAuth`, `HeaderApiKeyAuth`, `QueryApiKeyAuth`, `HmacAuth`. |
76
+ | 🔁 **Automatic Retries** | Exponential backoff on `429/500/502/503/504`, mounted transparently on every session. |
77
+ | 📄 **Smart Pagination** | Auto-detects Link-header (GitHub-style) and cursor/token (`meta.next_token`) pagination. |
78
+ | ⚠️ **Typed Exceptions** | `RateLimitError`, `AuthenticationError`, `ClientError`, `ServerError`, `RequestTimeoutError`. |
79
+ | 📦 **Ready-to-use Clients** | GitHub, Gmail, GoogleCalendar out of the box. |
80
+
81
+ ---
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ pip install hakiapi
87
+
88
+ ```
89
+
90
+ Requires **Python 3.10+**.
91
+
92
+ ---
93
+
94
+ ## Quick Start
95
+
96
+ ### 1. The Full-Cycle OAuth 2.0 Engine (Google Calendar)
97
+
98
+ Stop forcing users to copy and paste authorization codes. HakiAPI handles the complete browser flow, securely saves the state to an atomic file vault, and **automatically refreshes expired tokens in the background.**
99
+
100
+ ```python
101
+ import os
102
+ from hakiapi.clients.google_calendar import GoogleCalendarClient
103
+ from hakiapi.core.oauth import GoogleOAuthFlow, FileTokenStore
104
+
105
+ # 1. Set up the secure vault and flow logic
106
+ oauth_flow = GoogleOAuthFlow(
107
+ client_id=os.environ.get("GOOGLE_CLIENT_ID"),
108
+ client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"),
109
+ scopes=["https://www.googleapis.com/auth/calendar.readonly"],
110
+ store=FileTokenStore("my_secure_token.json"),
111
+ redirect_port=8765,
112
+ )
113
+
114
+ # 2. .get_token() does the heavy lifting:
115
+ # It loads the token if valid, silently refreshes if expired,
116
+ # or opens the browser and spins up a local interceptor if missing!
117
+ token = oauth_flow.get_token()
118
+
119
+ # 3. Initialize your client
120
+ with GoogleCalendarClient(token=token.access_token) as calendar:
121
+ for event in calendar.events.upcoming(max_results=3):
122
+ print(event.get("summary"))
123
+
124
+ ```
125
+
126
+ ### 2. Automatic Pagination (GitHub)
127
+
128
+ Forget page numbers, `while` loops, and manually checking for a `next` page. HakiAPI follows Link headers and cursor pagination automatically, lazily yielding results:
129
+
130
+ ```python
131
+ from hakiapi.clients.github import GitHubClient
132
+
133
+ with GitHubClient() as github:
134
+ # Lazily iterates through every repository, handling all HTTP requests behind the scenes
135
+ for repo in github.get_all_user_repos("torvalds"):
136
+ print(repo["name"])
137
+
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Exception Handling
143
+
144
+ Never check HTTP status codes manually again. Every exception inherits from `HakiAPIError`, carrying the `status_code` and original `response` object.
145
+
146
+ ```python
147
+ from hakiapi.core.exceptions import AuthenticationError, RateLimitError, ServerError
148
+
149
+ try:
150
+ github.get_user("torvalds")
151
+ except RateLimitError as e:
152
+ print(f"Rate limited — retry after {e.retry_after}s")
153
+ except AuthenticationError:
154
+ print("Invalid credentials.")
155
+ except ServerError:
156
+ print("GitHub is currently unavailable.")
157
+
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Architecture & Project Structure
163
+
164
+ HakiAPI is designed with strict separation of concerns. Every service client inherits production-ready infrastructure automatically.
165
+
166
+ ```text
167
+ hakiapi/
168
+ ├── core/
169
+ │ ├── oauth/ # The Self-Healing OAuth 2.0 Engine
170
+ │ │ ├── google.py # Local server interceptor & CSRF protection
171
+ │ │ ├── token_store.py # Atomic file vault for token persistence
172
+ │ │ └── refresh.py # Background token refresh logic
173
+ │ ├── auth.py # Static auth strategies (Bearer, HMAC, API Keys)
174
+ │ ├── retry.py # Exponential-backoff HTTPAdapter factory
175
+ │ ├── paginator.py # Link-header + cursor/token pagination
176
+ │ ├── base_client.py # Request lifecycle & session management
177
+ │ └── exceptions.py # Typed exception hierarchy
178
+ │
179
+ └── clients/
180
+ ├── github.py # GitHub API implementation
181
+ ├── gmail.py # Gmail API implementation
182
+ └── google_calendar.py # Google Calendar implementation
183
+
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Create Your Own Client
189
+
190
+ Creating a new SDK is intentionally simple: subclass `BaseAPIClient`, point it at a base URL, and define your endpoints as plain methods.
191
+
192
+ ```python
193
+ from hakiapi import BaseAPIClient
194
+
195
+ class WeatherClient(BaseAPIClient):
196
+ def __init__(self, **kwargs):
197
+ super().__init__(base_url="https://api.open-meteo.com/v1", **kwargs)
198
+
199
+ def get_weather(self, latitude: float, longitude: float):
200
+ return self.get(
201
+ "forecast",
202
+ params={
203
+ "latitude": latitude,
204
+ "longitude": longitude,
205
+ "current_weather": True,
206
+ },
207
+ )
208
+
209
+ if __name__ == "__main__":
210
+ # Hyderabad, Telangana, India
211
+ with WeatherClient() as client:
212
+ weather = client.get_weather(latitude=17.385, longitude=78.4867)
213
+ print(weather["current_weather"])
214
+
215
+ ```
216
+
217
+ ### Output
218
+
219
+ ```json
220
+ {
221
+ "time": "2026-07-24T22:00",
222
+ "interval": 900,
223
+ "temperature": 30.6,
224
+ "windspeed": 13.9,
225
+ "winddirection": 271,
226
+ "is_day": 0,
227
+ "weathercode": 51
228
+ }
229
+
230
+ ```
231
+
232
+ *Authentication, retries, pagination, sessions, and exceptions are entirely inherited — you only write the endpoint logic.*
233
+
234
+ ---
235
+
236
+ ## Design Principles
237
+
238
+ * Infrastructure should be written once.
239
+ * API clients should remain lightweight.
240
+ * Explicit is better than magical.
241
+ * Strong typing improves maintainability.
242
+ * Production readiness should be the default, not an afterthought.
243
+ * Developer experience matters as much as correctness.
244
+
245
+ ---
246
+
247
+ ## Testing
248
+
249
+ ```bash
250
+ pip install hakiapi[dev]
251
+ pytest
252
+
253
+ ```
254
+
255
+ * ✅ **280+ tests passing**
256
+ * ✅ Core framework covered (auth, retry, paginator, base client, exceptions)
257
+ * ✅ Full OAuth 2.0 engine mocked and covered
258
+ * ✅ Atomic file vault concurrency tested
259
+ * ✅ Client implementations covered
260
+
261
+ ---
262
+
263
+ ## Roadmap
264
+
265
+ **Completed**
266
+
267
+ * [x] Base API framework (`BaseAPIClient`)
268
+ * [x] Authentication system (Bearer, Header, Query, HMAC)
269
+ * [x] Retry engine with exponential backoff
270
+ * [x] Automatic pagination (Link header + cursor/token)
271
+ * [x] Typed exception hierarchy
272
+ * [x] Full-cycle OAuth 2.0 Engine (Local server, Vault, Auto-Refresh)
273
+ * [x] GitHub, Gmail, & Google Calendar clients
274
+
275
+ **Planned**
276
+
277
+ * [ ] Stripe client
278
+ * [ ] Twitter/X client
279
+ * [ ] Async client (`httpx`-based)
280
+ * [ ] Plugin system
281
+
282
+ ---
283
+
284
+ ## Contributing
285
+
286
+ 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.
287
+
288
+ ---
289
+
290
+ ## License
291
+
292
+ MIT License — see [LICENSE](https://www.google.com/search?q=LICENSE) for details.
293
+
294
+ ---
295
+
296
+ ### ⭐ If HakiAPI saved you from rewriting the same API client for the tenth time, consider giving it a star.
297
+
298
+ It helps more developers discover the project and motivates future development.
299
+
300
+ Built with ❤️ by **Gugilla Aakash**
@@ -0,0 +1,273 @@
1
+ <div align="center">
2
+
3
+ # 🚀 HakiAPI
4
+
5
+ ### Build production-grade Python API SDKs — not boilerplate.
6
+
7
+ Authentication · Self-Healing OAuth 2.0 · Retries · Pagination · Typed Exceptions
8
+
9
+ [![PyPI](https://img.shields.io/pypi/v/hakiapi?style=for-the-badge)](https://pypi.org/project/hakiapi/)
10
+ [![Python](https://img.shields.io/pypi/pyversions/hakiapi?style=for-the-badge)](https://pypi.org/project/hakiapi/)
11
+ [![License](https://img.shields.io/github/license/Gugilla-Aakash/hakiapi?style=for-the-badge)](LICENSE)
12
+ [![Tests](https://img.shields.io/badge/tests-280%2B_passing-success?style=for-the-badge)](#testing)
13
+ [![Typing](https://img.shields.io/badge/typing-fully_typed-blue?style=for-the-badge)](#features)
14
+ [![Downloads](https://img.shields.io/pypi/dm/hakiapi?style=for-the-badge)](https://pypistats.org/packages/hakiapi)
15
+
16
+ **Stop rewriting authentication, retries, and pagination for every API client you build.**
17
+
18
+ [Installation](#installation) • [Quick Start](#quick-start) • [Features](#features) • [Architecture](#architecture) • [Create Your Own Client](#create-your-own-client) • [Roadmap](#roadmap)
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## Why HakiAPI?
25
+
26
+ 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.
27
+
28
+ HakiAPI extracts all of that into one reusable core, so every client you build on top of it inherits enterprise-grade behavior automatically. Instead of writing infrastructure, you write business logic.
29
+
30
+ ### Without HakiAPI vs. with HakiAPI
31
+
32
+ | | Raw `requests` | HakiAPI |
33
+ |---|---|---|
34
+ | **OAuth 2.0 Flow** | Copy-paste URLs, manually spin up servers, hand-roll refreshes | Fully automated local interceptor + atomic token vault + auto-refresh |
35
+ | **Retry Logic** | Write your own `urllib3.Retry` + `HTTPAdapter` wiring | Built into `BaseAPIClient` with exponential backoff on 429/50x |
36
+ | **Static Auth** | Reimplement Bearer/HMAC/API keys per project | 4 reusable `AuthBase` strategies, drop-in |
37
+ | **Error Handling** | Manually check `response.status_code` everywhere | Raised as statically-typed, catchable exceptions |
38
+ | **Pagination** | Write a custom `while` loop per API's pagination style | Auto-detects Link-header & cursor pagination, iterated lazily |
39
+
40
+ ---
41
+
42
+ ## ✨ Features
43
+
44
+ | Feature | Details |
45
+ |---|---|
46
+ | 🔄 **Self-Healing OAuth 2.0** | Interactive local server interceptor, CSRF-protected browser flow, and background token refreshing. |
47
+ | 🗄️ **Atomic Token Vault** | `FileTokenStore` guarantees zero state corruption during concurrent writes using secure temp-file swapping. |
48
+ | 🔐 **Multiple Auth Strategies** | `BearerTokenAuth`, `HeaderApiKeyAuth`, `QueryApiKeyAuth`, `HmacAuth`. |
49
+ | 🔁 **Automatic Retries** | Exponential backoff on `429/500/502/503/504`, mounted transparently on every session. |
50
+ | 📄 **Smart Pagination** | Auto-detects Link-header (GitHub-style) and cursor/token (`meta.next_token`) pagination. |
51
+ | ⚠️ **Typed Exceptions** | `RateLimitError`, `AuthenticationError`, `ClientError`, `ServerError`, `RequestTimeoutError`. |
52
+ | 📦 **Ready-to-use Clients** | GitHub, Gmail, GoogleCalendar out of the box. |
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install hakiapi
60
+
61
+ ```
62
+
63
+ Requires **Python 3.10+**.
64
+
65
+ ---
66
+
67
+ ## Quick Start
68
+
69
+ ### 1. The Full-Cycle OAuth 2.0 Engine (Google Calendar)
70
+
71
+ Stop forcing users to copy and paste authorization codes. HakiAPI handles the complete browser flow, securely saves the state to an atomic file vault, and **automatically refreshes expired tokens in the background.**
72
+
73
+ ```python
74
+ import os
75
+ from hakiapi.clients.google_calendar import GoogleCalendarClient
76
+ from hakiapi.core.oauth import GoogleOAuthFlow, FileTokenStore
77
+
78
+ # 1. Set up the secure vault and flow logic
79
+ oauth_flow = GoogleOAuthFlow(
80
+ client_id=os.environ.get("GOOGLE_CLIENT_ID"),
81
+ client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"),
82
+ scopes=["https://www.googleapis.com/auth/calendar.readonly"],
83
+ store=FileTokenStore("my_secure_token.json"),
84
+ redirect_port=8765,
85
+ )
86
+
87
+ # 2. .get_token() does the heavy lifting:
88
+ # It loads the token if valid, silently refreshes if expired,
89
+ # or opens the browser and spins up a local interceptor if missing!
90
+ token = oauth_flow.get_token()
91
+
92
+ # 3. Initialize your client
93
+ with GoogleCalendarClient(token=token.access_token) as calendar:
94
+ for event in calendar.events.upcoming(max_results=3):
95
+ print(event.get("summary"))
96
+
97
+ ```
98
+
99
+ ### 2. Automatic Pagination (GitHub)
100
+
101
+ Forget page numbers, `while` loops, and manually checking for a `next` page. HakiAPI follows Link headers and cursor pagination automatically, lazily yielding results:
102
+
103
+ ```python
104
+ from hakiapi.clients.github import GitHubClient
105
+
106
+ with GitHubClient() as github:
107
+ # Lazily iterates through every repository, handling all HTTP requests behind the scenes
108
+ for repo in github.get_all_user_repos("torvalds"):
109
+ print(repo["name"])
110
+
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Exception Handling
116
+
117
+ Never check HTTP status codes manually again. Every exception inherits from `HakiAPIError`, carrying the `status_code` and original `response` object.
118
+
119
+ ```python
120
+ from hakiapi.core.exceptions import AuthenticationError, RateLimitError, ServerError
121
+
122
+ try:
123
+ github.get_user("torvalds")
124
+ except RateLimitError as e:
125
+ print(f"Rate limited — retry after {e.retry_after}s")
126
+ except AuthenticationError:
127
+ print("Invalid credentials.")
128
+ except ServerError:
129
+ print("GitHub is currently unavailable.")
130
+
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Architecture & Project Structure
136
+
137
+ HakiAPI is designed with strict separation of concerns. Every service client inherits production-ready infrastructure automatically.
138
+
139
+ ```text
140
+ hakiapi/
141
+ ├── core/
142
+ │ ├── oauth/ # The Self-Healing OAuth 2.0 Engine
143
+ │ │ ├── google.py # Local server interceptor & CSRF protection
144
+ │ │ ├── token_store.py # Atomic file vault for token persistence
145
+ │ │ └── refresh.py # Background token refresh logic
146
+ │ ├── auth.py # Static auth strategies (Bearer, HMAC, API Keys)
147
+ │ ├── retry.py # Exponential-backoff HTTPAdapter factory
148
+ │ ├── paginator.py # Link-header + cursor/token pagination
149
+ │ ├── base_client.py # Request lifecycle & session management
150
+ │ └── exceptions.py # Typed exception hierarchy
151
+ │
152
+ └── clients/
153
+ ├── github.py # GitHub API implementation
154
+ ├── gmail.py # Gmail API implementation
155
+ └── google_calendar.py # Google Calendar implementation
156
+
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Create Your Own Client
162
+
163
+ Creating a new SDK is intentionally simple: subclass `BaseAPIClient`, point it at a base URL, and define your endpoints as plain methods.
164
+
165
+ ```python
166
+ from hakiapi import BaseAPIClient
167
+
168
+ class WeatherClient(BaseAPIClient):
169
+ def __init__(self, **kwargs):
170
+ super().__init__(base_url="https://api.open-meteo.com/v1", **kwargs)
171
+
172
+ def get_weather(self, latitude: float, longitude: float):
173
+ return self.get(
174
+ "forecast",
175
+ params={
176
+ "latitude": latitude,
177
+ "longitude": longitude,
178
+ "current_weather": True,
179
+ },
180
+ )
181
+
182
+ if __name__ == "__main__":
183
+ # Hyderabad, Telangana, India
184
+ with WeatherClient() as client:
185
+ weather = client.get_weather(latitude=17.385, longitude=78.4867)
186
+ print(weather["current_weather"])
187
+
188
+ ```
189
+
190
+ ### Output
191
+
192
+ ```json
193
+ {
194
+ "time": "2026-07-24T22:00",
195
+ "interval": 900,
196
+ "temperature": 30.6,
197
+ "windspeed": 13.9,
198
+ "winddirection": 271,
199
+ "is_day": 0,
200
+ "weathercode": 51
201
+ }
202
+
203
+ ```
204
+
205
+ *Authentication, retries, pagination, sessions, and exceptions are entirely inherited — you only write the endpoint logic.*
206
+
207
+ ---
208
+
209
+ ## Design Principles
210
+
211
+ * Infrastructure should be written once.
212
+ * API clients should remain lightweight.
213
+ * Explicit is better than magical.
214
+ * Strong typing improves maintainability.
215
+ * Production readiness should be the default, not an afterthought.
216
+ * Developer experience matters as much as correctness.
217
+
218
+ ---
219
+
220
+ ## Testing
221
+
222
+ ```bash
223
+ pip install hakiapi[dev]
224
+ pytest
225
+
226
+ ```
227
+
228
+ * ✅ **280+ tests passing**
229
+ * ✅ Core framework covered (auth, retry, paginator, base client, exceptions)
230
+ * ✅ Full OAuth 2.0 engine mocked and covered
231
+ * ✅ Atomic file vault concurrency tested
232
+ * ✅ Client implementations covered
233
+
234
+ ---
235
+
236
+ ## Roadmap
237
+
238
+ **Completed**
239
+
240
+ * [x] Base API framework (`BaseAPIClient`)
241
+ * [x] Authentication system (Bearer, Header, Query, HMAC)
242
+ * [x] Retry engine with exponential backoff
243
+ * [x] Automatic pagination (Link header + cursor/token)
244
+ * [x] Typed exception hierarchy
245
+ * [x] Full-cycle OAuth 2.0 Engine (Local server, Vault, Auto-Refresh)
246
+ * [x] GitHub, Gmail, & Google Calendar clients
247
+
248
+ **Planned**
249
+
250
+ * [ ] Stripe client
251
+ * [ ] Twitter/X client
252
+ * [ ] Async client (`httpx`-based)
253
+ * [ ] Plugin system
254
+
255
+ ---
256
+
257
+ ## Contributing
258
+
259
+ 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.
260
+
261
+ ---
262
+
263
+ ## License
264
+
265
+ MIT License — see [LICENSE](https://www.google.com/search?q=LICENSE) for details.
266
+
267
+ ---
268
+
269
+ ### ⭐ If HakiAPI saved you from rewriting the same API client for the tenth time, consider giving it a star.
270
+
271
+ It helps more developers discover the project and motivates future development.
272
+
273
+ Built with ❤️ by **Gugilla Aakash**
@@ -1,7 +1,9 @@
1
- from typing import Any, Iterator
1
+ from collections.abc import Iterator
2
+ from typing import Any
2
3
 
3
- from hakiapi.core.base_client import BaseAPIClient
4
4
  from hakiapi.core.auth import BearerTokenAuth
5
+ from hakiapi.core.base_client import BaseAPIClient
6
+ from hakiapi.core.exceptions import HakiAPIError
5
7
  from hakiapi.core.paginator import paginate
6
8
 
7
9
 
@@ -27,6 +29,32 @@ class GitHubClient(BaseAPIClient):
27
29
  """Fetch a GitHub user's profile."""
28
30
  return self.get(f"users/{username}", **kwargs)
29
31
 
32
+ def search_users(self, query: str, **kwargs: Any) -> dict[str, Any]:
33
+ """
34
+ Search GitHub users matching a query string.
35
+
36
+ Returns the raw GitHub search response, e.g.:
37
+ {"total_count": N, "incomplete_results": bool, "items": [...]}
38
+ """
39
+ # Safely cast to a dictionary, protecting against list-of-tuples
40
+ params: dict[str, Any] = dict(kwargs.pop("params", None) or {})
41
+ params["q"] = query
42
+
43
+ return self.get("search/users", params=params, **kwargs)
44
+
45
+ def get_all_search_users(
46
+ self, query: str, **kwargs: Any
47
+ ) -> Iterator[dict[str, Any]]:
48
+ """
49
+ Search GitHub users matching a query string, auto-paginating
50
+ through every page of results (GitHub's Link-header pagination).
51
+ """
52
+ params: dict[str, Any] = dict(kwargs.pop("params", None) or {})
53
+ params["q"] = query
54
+
55
+ # Delegated directly to the paginator using yield from
56
+ yield from paginate(self, "search/users", params=params, **kwargs)
57
+
30
58
  def get_user_repos(self, username: str, **kwargs: Any) -> list[dict[str, Any]]:
31
59
  """Fetch a single page of public repositories of a GitHub user."""
32
60
  return self.get(f"users/{username}/repos", **kwargs)
@@ -35,7 +63,8 @@ class GitHubClient(BaseAPIClient):
35
63
  self, username: str, **kwargs: Any
36
64
  ) -> Iterator[dict[str, Any]]:
37
65
  """Fetch ALL public repositories using automatic pagination."""
38
- return paginate(self, f"users/{username}/repos", **kwargs)
66
+ # Delegated directly to the paginator using yield from
67
+ yield from paginate(self, f"users/{username}/repos", **kwargs)
39
68
 
40
69
  def get_repo_languages(
41
70
  self, owner: str, repo: str, **kwargs: Any
@@ -67,7 +96,7 @@ class GitHubClient(BaseAPIClient):
67
96
  aggregate_languages[lang] = (
68
97
  aggregate_languages.get(lang, 0) + bytes_count
69
98
  )
70
- except Exception:
99
+ except HakiAPIError:
71
100
  # Shield the iteration sequence if a single repo is deleted or inaccessible
72
101
  continue
73
102
 
@@ -51,11 +51,15 @@ def paginate(
51
51
  items = data["messages"]
52
52
  elif isinstance(data.get("items"), list):
53
53
  items = data["items"]
54
+
55
+ # Fixed google email pagination bug when it returns 0 results
56
+ elif data.get("resultSizeEstimate") == 0:
57
+ break
54
58
  else:
55
- raise ValueError(
56
- "Paginator expected a list response, or a dict with a "
57
- "'data', 'messages', or 'items' list."
58
- )
59
+ print("Unexpected response:")
60
+ print(data)
61
+
62
+ raise ValueError(f"Unexpected pagination response: {data}")
59
63
  else:
60
64
  raise ValueError("Unexpected response format.")
61
65