hookbase 1.0.0__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.
- hookbase-1.0.0/.gitignore +13 -0
- hookbase-1.0.0/LICENSE +21 -0
- hookbase-1.0.0/PKG-INFO +268 -0
- hookbase-1.0.0/README.md +234 -0
- hookbase-1.0.0/examples/async_usage.py +36 -0
- hookbase-1.0.0/examples/basic_inbound.py +41 -0
- hookbase-1.0.0/examples/error_handling.py +52 -0
- hookbase-1.0.0/examples/send_webhooks.py +55 -0
- hookbase-1.0.0/examples/verify_webhooks.py +66 -0
- hookbase-1.0.0/pyproject.toml +76 -0
- hookbase-1.0.0/src/hookbase/__init__.py +39 -0
- hookbase-1.0.0/src/hookbase/_client.py +309 -0
- hookbase-1.0.0/src/hookbase/_constants.py +4 -0
- hookbase-1.0.0/src/hookbase/_pagination.py +355 -0
- hookbase-1.0.0/src/hookbase/_version.py +1 -0
- hookbase-1.0.0/src/hookbase/client.py +270 -0
- hookbase-1.0.0/src/hookbase/errors.py +166 -0
- hookbase-1.0.0/src/hookbase/models/__init__.py +203 -0
- hookbase-1.0.0/src/hookbase/models/_base.py +14 -0
- hookbase-1.0.0/src/hookbase/models/analytics.py +22 -0
- hookbase-1.0.0/src/hookbase/models/api_keys.py +36 -0
- hookbase-1.0.0/src/hookbase/models/applications.py +26 -0
- hookbase-1.0.0/src/hookbase/models/common.py +42 -0
- hookbase-1.0.0/src/hookbase/models/cron_jobs.py +78 -0
- hookbase-1.0.0/src/hookbase/models/deliveries.py +53 -0
- hookbase-1.0.0/src/hookbase/models/destinations.py +71 -0
- hookbase-1.0.0/src/hookbase/models/dlq.py +60 -0
- hookbase-1.0.0/src/hookbase/models/endpoints.py +76 -0
- hookbase-1.0.0/src/hookbase/models/event_types.py +37 -0
- hookbase-1.0.0/src/hookbase/models/events.py +70 -0
- hookbase-1.0.0/src/hookbase/models/filters.py +60 -0
- hookbase-1.0.0/src/hookbase/models/messages.py +73 -0
- hookbase-1.0.0/src/hookbase/models/organizations.py +32 -0
- hookbase-1.0.0/src/hookbase/models/routes.py +97 -0
- hookbase-1.0.0/src/hookbase/models/schemas.py +41 -0
- hookbase-1.0.0/src/hookbase/models/sources.py +72 -0
- hookbase-1.0.0/src/hookbase/models/subscriptions.py +22 -0
- hookbase-1.0.0/src/hookbase/models/transforms.py +49 -0
- hookbase-1.0.0/src/hookbase/models/tunnels.py +19 -0
- hookbase-1.0.0/src/hookbase/py.typed +0 -0
- hookbase-1.0.0/src/hookbase/resources/__init__.py +43 -0
- hookbase-1.0.0/src/hookbase/resources/_base.py +80 -0
- hookbase-1.0.0/src/hookbase/resources/analytics.py +56 -0
- hookbase-1.0.0/src/hookbase/resources/api_keys.py +38 -0
- hookbase-1.0.0/src/hookbase/resources/applications.py +116 -0
- hookbase-1.0.0/src/hookbase/resources/cron_jobs.py +104 -0
- hookbase-1.0.0/src/hookbase/resources/deliveries.py +81 -0
- hookbase-1.0.0/src/hookbase/resources/destinations.py +138 -0
- hookbase-1.0.0/src/hookbase/resources/dlq.py +99 -0
- hookbase-1.0.0/src/hookbase/resources/endpoints.py +146 -0
- hookbase-1.0.0/src/hookbase/resources/event_types.py +92 -0
- hookbase-1.0.0/src/hookbase/resources/events.py +108 -0
- hookbase-1.0.0/src/hookbase/resources/filters.py +112 -0
- hookbase-1.0.0/src/hookbase/resources/message_log.py +148 -0
- hookbase-1.0.0/src/hookbase/resources/messages.py +70 -0
- hookbase-1.0.0/src/hookbase/resources/organizations.py +110 -0
- hookbase-1.0.0/src/hookbase/resources/routes.py +177 -0
- hookbase-1.0.0/src/hookbase/resources/schemas.py +72 -0
- hookbase-1.0.0/src/hookbase/resources/sources.py +150 -0
- hookbase-1.0.0/src/hookbase/resources/subscriptions.py +114 -0
- hookbase-1.0.0/src/hookbase/resources/transforms.py +119 -0
- hookbase-1.0.0/src/hookbase/resources/tunnels.py +51 -0
- hookbase-1.0.0/src/hookbase/webhook.py +165 -0
- hookbase-1.0.0/tests/__init__.py +0 -0
- hookbase-1.0.0/tests/conftest.py +68 -0
- hookbase-1.0.0/tests/resources/__init__.py +0 -0
- hookbase-1.0.0/tests/resources/test_applications.py +77 -0
- hookbase-1.0.0/tests/resources/test_destinations.py +72 -0
- hookbase-1.0.0/tests/resources/test_dlq.py +88 -0
- hookbase-1.0.0/tests/resources/test_messages.py +84 -0
- hookbase-1.0.0/tests/resources/test_sources.py +112 -0
- hookbase-1.0.0/tests/test_client.py +65 -0
- hookbase-1.0.0/tests/test_errors.py +104 -0
- hookbase-1.0.0/tests/test_pagination.py +84 -0
- hookbase-1.0.0/tests/test_webhook.py +151 -0
hookbase-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hookbase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hookbase-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hookbase
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for the Hookbase webhook management platform
|
|
5
|
+
Project-URL: Homepage, https://hookbase.app
|
|
6
|
+
Project-URL: Documentation, https://docs.hookbase.app/sdk/python
|
|
7
|
+
Project-URL: Repository, https://github.com/hookbase/hookbase-python
|
|
8
|
+
Project-URL: Issues, https://github.com/hookbase/hookbase-python/issues
|
|
9
|
+
Author-email: Hookbase <support@hookbase.app>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx<1.0.0,>=0.25.0
|
|
26
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Hookbase Python SDK
|
|
36
|
+
|
|
37
|
+
The official Python SDK for the [Hookbase](https://hookbase.app) webhook management platform.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install hookbase
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from hookbase import Hookbase
|
|
49
|
+
|
|
50
|
+
client = Hookbase(api_key="whr_your_api_key")
|
|
51
|
+
|
|
52
|
+
# List webhook sources
|
|
53
|
+
sources = client.sources.list()
|
|
54
|
+
for source in sources.data:
|
|
55
|
+
print(f"{source.name} ({source.provider})")
|
|
56
|
+
|
|
57
|
+
# Send an outbound webhook
|
|
58
|
+
result = client.outbound.messages.send(
|
|
59
|
+
"app_123",
|
|
60
|
+
event_type="order.created",
|
|
61
|
+
payload={"orderId": "456", "amount": 99.99},
|
|
62
|
+
)
|
|
63
|
+
print(f"Sent: {result.event_id}")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Async Support
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from hookbase import AsyncHookbase
|
|
70
|
+
|
|
71
|
+
async with AsyncHookbase(api_key="whr_...") as client:
|
|
72
|
+
sources = await client.sources.list()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### Client Initialization
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from hookbase import Hookbase
|
|
81
|
+
|
|
82
|
+
client = Hookbase(
|
|
83
|
+
api_key="whr_...", # Required
|
|
84
|
+
base_url="https://...", # Default: https://api.hookbase.app
|
|
85
|
+
timeout=30.0, # Seconds (default: 30)
|
|
86
|
+
max_retries=3, # Default: 3
|
|
87
|
+
debug=False, # Log requests
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Inbound Webhooks
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Sources
|
|
95
|
+
client.sources.list(search="github", provider="github")
|
|
96
|
+
client.sources.create({"name": "GitHub", "slug": "github", "provider": "github"})
|
|
97
|
+
client.sources.get("src_id")
|
|
98
|
+
client.sources.update("src_id", {"name": "Updated"})
|
|
99
|
+
client.sources.delete("src_id")
|
|
100
|
+
client.sources.rotate_secret("src_id")
|
|
101
|
+
|
|
102
|
+
# Destinations
|
|
103
|
+
client.destinations.list()
|
|
104
|
+
client.destinations.create({"name": "Backend", "url": "https://..."})
|
|
105
|
+
client.destinations.test("dst_id")
|
|
106
|
+
|
|
107
|
+
# Routes
|
|
108
|
+
client.routes.create({"name": "Route", "sourceId": "src_id", "destinationId": "dst_id"})
|
|
109
|
+
client.routes.get_circuit_status("route_id")
|
|
110
|
+
client.routes.reset_circuit("route_id")
|
|
111
|
+
|
|
112
|
+
# Events & Deliveries
|
|
113
|
+
client.events.list(source_id="src_id", from_date="2024-01-01")
|
|
114
|
+
client.events.get("evt_id") # Includes payload and deliveries
|
|
115
|
+
client.deliveries.replay("del_id")
|
|
116
|
+
client.deliveries.bulk_replay(["del_1", "del_2"])
|
|
117
|
+
|
|
118
|
+
# Transforms & Filters
|
|
119
|
+
client.transforms.create({"name": "Extract", "transformType": "jsonata", "code": "$.data"})
|
|
120
|
+
client.transforms.test("txf_id", payload={"data": {"key": "value"}})
|
|
121
|
+
client.filters.test([{"field": "$.type", "operator": "eq", "value": "order"}], payload={...})
|
|
122
|
+
|
|
123
|
+
# Schemas
|
|
124
|
+
client.schemas.create({"name": "OrderSchema", "jsonSchema": {"type": "object", ...}})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Outbound Webhooks
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Applications
|
|
131
|
+
client.outbound.applications.create({"name": "Acme", "uid": "cust_123"})
|
|
132
|
+
client.outbound.applications.get_by_external_id("cust_123")
|
|
133
|
+
|
|
134
|
+
# Endpoints
|
|
135
|
+
client.outbound.endpoints.create("app_id", {"url": "https://..."})
|
|
136
|
+
client.outbound.endpoints.rotate_secret("ep_id", grace_period=3600)
|
|
137
|
+
|
|
138
|
+
# Event Types & Subscriptions
|
|
139
|
+
client.outbound.event_types.create({"name": "order.created", "category": "orders"})
|
|
140
|
+
client.outbound.subscriptions.create({"endpointId": "ep_id", "eventTypeId": "et_id"})
|
|
141
|
+
client.outbound.subscriptions.bulk_create("ep_id", ["et_1", "et_2"])
|
|
142
|
+
|
|
143
|
+
# Send Events
|
|
144
|
+
client.outbound.messages.send("app_id", event_type="order.created", payload={...})
|
|
145
|
+
|
|
146
|
+
# Message Log
|
|
147
|
+
client.outbound.message_log.list(application_id="app_id")
|
|
148
|
+
client.outbound.message_log.list_attempts("msg_id")
|
|
149
|
+
client.outbound.message_log.stats()
|
|
150
|
+
|
|
151
|
+
# Dead Letter Queue
|
|
152
|
+
client.outbound.dlq.list()
|
|
153
|
+
client.outbound.dlq.stats()
|
|
154
|
+
client.outbound.dlq.retry("dlq_id")
|
|
155
|
+
client.outbound.dlq.retry_bulk(["dlq_1", "dlq_2"])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Admin
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# Organizations
|
|
162
|
+
client.organizations.list()
|
|
163
|
+
client.organizations.list_members("org_id")
|
|
164
|
+
client.organizations.invite("org_id", "user@example.com", "member")
|
|
165
|
+
|
|
166
|
+
# API Keys
|
|
167
|
+
client.api_keys.create({"name": "CI Key", "scopes": ["read", "write"]})
|
|
168
|
+
|
|
169
|
+
# Analytics
|
|
170
|
+
client.analytics.dashboard(range="30d")
|
|
171
|
+
|
|
172
|
+
# Cron Jobs & Tunnels
|
|
173
|
+
client.cron_jobs.list()
|
|
174
|
+
client.tunnels.list()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Pagination
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# Offset-based (sources, destinations, routes, etc.)
|
|
181
|
+
page = client.sources.list(page_size=10)
|
|
182
|
+
print(f"Total: {page.total}, Page: {page.page}")
|
|
183
|
+
|
|
184
|
+
# Auto-paginate through all items
|
|
185
|
+
for source in page.auto_paging_iter():
|
|
186
|
+
print(source.name)
|
|
187
|
+
|
|
188
|
+
# Manual pagination
|
|
189
|
+
while page.has_more:
|
|
190
|
+
page = page.next_page()
|
|
191
|
+
|
|
192
|
+
# Cursor-based (applications, endpoints, subscriptions, etc.)
|
|
193
|
+
page = client.outbound.applications.list(limit=50)
|
|
194
|
+
for app in page.auto_paging_iter():
|
|
195
|
+
print(app.name)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Webhook Verification
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from hookbase import Webhook, WebhookVerificationError
|
|
202
|
+
|
|
203
|
+
wh = Webhook("whsec_your_secret")
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
payload = wh.verify(request_body, request_headers)
|
|
207
|
+
print(f"Verified: {payload}")
|
|
208
|
+
except WebhookVerificationError as e:
|
|
209
|
+
print(f"Invalid: {e}")
|
|
210
|
+
|
|
211
|
+
# Generate test headers
|
|
212
|
+
headers = wh.generate_test_headers('{"test": true}')
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Error Handling
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from hookbase import (
|
|
219
|
+
HookbaseError, # Base error
|
|
220
|
+
APIError, # API returned an error
|
|
221
|
+
AuthenticationError, # 401
|
|
222
|
+
ForbiddenError, # 403
|
|
223
|
+
NotFoundError, # 404
|
|
224
|
+
ValidationError, # 400/422
|
|
225
|
+
ConflictError, # 409
|
|
226
|
+
RateLimitError, # 429 (has retry_after)
|
|
227
|
+
TimeoutError, # Request timed out
|
|
228
|
+
NetworkError, # Connection failed
|
|
229
|
+
WebhookVerificationError,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
client.sources.get("src_id")
|
|
234
|
+
except NotFoundError:
|
|
235
|
+
print("Not found")
|
|
236
|
+
except RateLimitError as e:
|
|
237
|
+
print(f"Retry after {e.retry_after}s")
|
|
238
|
+
except APIError as e:
|
|
239
|
+
print(f"{e.status_code}: {e} (request_id={e.request_id})")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Pydantic Models
|
|
243
|
+
|
|
244
|
+
All responses are typed Pydantic models with camelCase alias support:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from hookbase.models import Source, CreateSourceParams
|
|
248
|
+
|
|
249
|
+
# Use Pydantic models for type safety
|
|
250
|
+
params = CreateSourceParams(name="GitHub", provider="github")
|
|
251
|
+
source = client.sources.create(params)
|
|
252
|
+
|
|
253
|
+
# Or use plain dicts
|
|
254
|
+
source = client.sources.create({"name": "GitHub", "provider": "github"})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Development
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
pip install -e ".[dev]"
|
|
261
|
+
pytest tests/ -v
|
|
262
|
+
mypy src/hookbase
|
|
263
|
+
ruff check src/ tests/
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT
|
hookbase-1.0.0/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Hookbase Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [Hookbase](https://hookbase.app) webhook management platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install hookbase
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from hookbase import Hookbase
|
|
15
|
+
|
|
16
|
+
client = Hookbase(api_key="whr_your_api_key")
|
|
17
|
+
|
|
18
|
+
# List webhook sources
|
|
19
|
+
sources = client.sources.list()
|
|
20
|
+
for source in sources.data:
|
|
21
|
+
print(f"{source.name} ({source.provider})")
|
|
22
|
+
|
|
23
|
+
# Send an outbound webhook
|
|
24
|
+
result = client.outbound.messages.send(
|
|
25
|
+
"app_123",
|
|
26
|
+
event_type="order.created",
|
|
27
|
+
payload={"orderId": "456", "amount": 99.99},
|
|
28
|
+
)
|
|
29
|
+
print(f"Sent: {result.event_id}")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Async Support
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from hookbase import AsyncHookbase
|
|
36
|
+
|
|
37
|
+
async with AsyncHookbase(api_key="whr_...") as client:
|
|
38
|
+
sources = await client.sources.list()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API Reference
|
|
42
|
+
|
|
43
|
+
### Client Initialization
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from hookbase import Hookbase
|
|
47
|
+
|
|
48
|
+
client = Hookbase(
|
|
49
|
+
api_key="whr_...", # Required
|
|
50
|
+
base_url="https://...", # Default: https://api.hookbase.app
|
|
51
|
+
timeout=30.0, # Seconds (default: 30)
|
|
52
|
+
max_retries=3, # Default: 3
|
|
53
|
+
debug=False, # Log requests
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Inbound Webhooks
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Sources
|
|
61
|
+
client.sources.list(search="github", provider="github")
|
|
62
|
+
client.sources.create({"name": "GitHub", "slug": "github", "provider": "github"})
|
|
63
|
+
client.sources.get("src_id")
|
|
64
|
+
client.sources.update("src_id", {"name": "Updated"})
|
|
65
|
+
client.sources.delete("src_id")
|
|
66
|
+
client.sources.rotate_secret("src_id")
|
|
67
|
+
|
|
68
|
+
# Destinations
|
|
69
|
+
client.destinations.list()
|
|
70
|
+
client.destinations.create({"name": "Backend", "url": "https://..."})
|
|
71
|
+
client.destinations.test("dst_id")
|
|
72
|
+
|
|
73
|
+
# Routes
|
|
74
|
+
client.routes.create({"name": "Route", "sourceId": "src_id", "destinationId": "dst_id"})
|
|
75
|
+
client.routes.get_circuit_status("route_id")
|
|
76
|
+
client.routes.reset_circuit("route_id")
|
|
77
|
+
|
|
78
|
+
# Events & Deliveries
|
|
79
|
+
client.events.list(source_id="src_id", from_date="2024-01-01")
|
|
80
|
+
client.events.get("evt_id") # Includes payload and deliveries
|
|
81
|
+
client.deliveries.replay("del_id")
|
|
82
|
+
client.deliveries.bulk_replay(["del_1", "del_2"])
|
|
83
|
+
|
|
84
|
+
# Transforms & Filters
|
|
85
|
+
client.transforms.create({"name": "Extract", "transformType": "jsonata", "code": "$.data"})
|
|
86
|
+
client.transforms.test("txf_id", payload={"data": {"key": "value"}})
|
|
87
|
+
client.filters.test([{"field": "$.type", "operator": "eq", "value": "order"}], payload={...})
|
|
88
|
+
|
|
89
|
+
# Schemas
|
|
90
|
+
client.schemas.create({"name": "OrderSchema", "jsonSchema": {"type": "object", ...}})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Outbound Webhooks
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Applications
|
|
97
|
+
client.outbound.applications.create({"name": "Acme", "uid": "cust_123"})
|
|
98
|
+
client.outbound.applications.get_by_external_id("cust_123")
|
|
99
|
+
|
|
100
|
+
# Endpoints
|
|
101
|
+
client.outbound.endpoints.create("app_id", {"url": "https://..."})
|
|
102
|
+
client.outbound.endpoints.rotate_secret("ep_id", grace_period=3600)
|
|
103
|
+
|
|
104
|
+
# Event Types & Subscriptions
|
|
105
|
+
client.outbound.event_types.create({"name": "order.created", "category": "orders"})
|
|
106
|
+
client.outbound.subscriptions.create({"endpointId": "ep_id", "eventTypeId": "et_id"})
|
|
107
|
+
client.outbound.subscriptions.bulk_create("ep_id", ["et_1", "et_2"])
|
|
108
|
+
|
|
109
|
+
# Send Events
|
|
110
|
+
client.outbound.messages.send("app_id", event_type="order.created", payload={...})
|
|
111
|
+
|
|
112
|
+
# Message Log
|
|
113
|
+
client.outbound.message_log.list(application_id="app_id")
|
|
114
|
+
client.outbound.message_log.list_attempts("msg_id")
|
|
115
|
+
client.outbound.message_log.stats()
|
|
116
|
+
|
|
117
|
+
# Dead Letter Queue
|
|
118
|
+
client.outbound.dlq.list()
|
|
119
|
+
client.outbound.dlq.stats()
|
|
120
|
+
client.outbound.dlq.retry("dlq_id")
|
|
121
|
+
client.outbound.dlq.retry_bulk(["dlq_1", "dlq_2"])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Admin
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Organizations
|
|
128
|
+
client.organizations.list()
|
|
129
|
+
client.organizations.list_members("org_id")
|
|
130
|
+
client.organizations.invite("org_id", "user@example.com", "member")
|
|
131
|
+
|
|
132
|
+
# API Keys
|
|
133
|
+
client.api_keys.create({"name": "CI Key", "scopes": ["read", "write"]})
|
|
134
|
+
|
|
135
|
+
# Analytics
|
|
136
|
+
client.analytics.dashboard(range="30d")
|
|
137
|
+
|
|
138
|
+
# Cron Jobs & Tunnels
|
|
139
|
+
client.cron_jobs.list()
|
|
140
|
+
client.tunnels.list()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Pagination
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Offset-based (sources, destinations, routes, etc.)
|
|
147
|
+
page = client.sources.list(page_size=10)
|
|
148
|
+
print(f"Total: {page.total}, Page: {page.page}")
|
|
149
|
+
|
|
150
|
+
# Auto-paginate through all items
|
|
151
|
+
for source in page.auto_paging_iter():
|
|
152
|
+
print(source.name)
|
|
153
|
+
|
|
154
|
+
# Manual pagination
|
|
155
|
+
while page.has_more:
|
|
156
|
+
page = page.next_page()
|
|
157
|
+
|
|
158
|
+
# Cursor-based (applications, endpoints, subscriptions, etc.)
|
|
159
|
+
page = client.outbound.applications.list(limit=50)
|
|
160
|
+
for app in page.auto_paging_iter():
|
|
161
|
+
print(app.name)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Webhook Verification
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from hookbase import Webhook, WebhookVerificationError
|
|
168
|
+
|
|
169
|
+
wh = Webhook("whsec_your_secret")
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
payload = wh.verify(request_body, request_headers)
|
|
173
|
+
print(f"Verified: {payload}")
|
|
174
|
+
except WebhookVerificationError as e:
|
|
175
|
+
print(f"Invalid: {e}")
|
|
176
|
+
|
|
177
|
+
# Generate test headers
|
|
178
|
+
headers = wh.generate_test_headers('{"test": true}')
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Error Handling
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from hookbase import (
|
|
185
|
+
HookbaseError, # Base error
|
|
186
|
+
APIError, # API returned an error
|
|
187
|
+
AuthenticationError, # 401
|
|
188
|
+
ForbiddenError, # 403
|
|
189
|
+
NotFoundError, # 404
|
|
190
|
+
ValidationError, # 400/422
|
|
191
|
+
ConflictError, # 409
|
|
192
|
+
RateLimitError, # 429 (has retry_after)
|
|
193
|
+
TimeoutError, # Request timed out
|
|
194
|
+
NetworkError, # Connection failed
|
|
195
|
+
WebhookVerificationError,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
client.sources.get("src_id")
|
|
200
|
+
except NotFoundError:
|
|
201
|
+
print("Not found")
|
|
202
|
+
except RateLimitError as e:
|
|
203
|
+
print(f"Retry after {e.retry_after}s")
|
|
204
|
+
except APIError as e:
|
|
205
|
+
print(f"{e.status_code}: {e} (request_id={e.request_id})")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Pydantic Models
|
|
209
|
+
|
|
210
|
+
All responses are typed Pydantic models with camelCase alias support:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from hookbase.models import Source, CreateSourceParams
|
|
214
|
+
|
|
215
|
+
# Use Pydantic models for type safety
|
|
216
|
+
params = CreateSourceParams(name="GitHub", provider="github")
|
|
217
|
+
source = client.sources.create(params)
|
|
218
|
+
|
|
219
|
+
# Or use plain dicts
|
|
220
|
+
source = client.sources.create({"name": "GitHub", "provider": "github"})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
pip install -e ".[dev]"
|
|
227
|
+
pytest tests/ -v
|
|
228
|
+
mypy src/hookbase
|
|
229
|
+
ruff check src/ tests/
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Async usage with AsyncHookbase."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from hookbase import AsyncHookbase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def main():
|
|
9
|
+
async with AsyncHookbase(api_key="whr_your_api_key") as client:
|
|
10
|
+
# List sources
|
|
11
|
+
sources = await client.sources.list()
|
|
12
|
+
print(f"Found {sources.total} sources")
|
|
13
|
+
|
|
14
|
+
for src in sources.data:
|
|
15
|
+
print(f" {src.name} ({src.provider})")
|
|
16
|
+
|
|
17
|
+
# Send an outbound webhook
|
|
18
|
+
result = await client.outbound.messages.send(
|
|
19
|
+
"app_123",
|
|
20
|
+
event_type="invoice.paid",
|
|
21
|
+
payload={"invoiceId": "inv_789", "amount": 49.99},
|
|
22
|
+
)
|
|
23
|
+
print(f"Sent event: {result.event_id}")
|
|
24
|
+
|
|
25
|
+
# Check DLQ
|
|
26
|
+
dlq_stats = await client.outbound.dlq.stats()
|
|
27
|
+
print(f"DLQ: {dlq_stats.total} messages")
|
|
28
|
+
|
|
29
|
+
# Paginate through all applications
|
|
30
|
+
page = await client.outbound.applications.list(limit=10)
|
|
31
|
+
async for app in page.auto_paging_iter():
|
|
32
|
+
print(f" App: {app.name} (uid={app.uid})")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Basic inbound webhook setup: create source -> destination -> route."""
|
|
2
|
+
|
|
3
|
+
from hookbase import Hookbase
|
|
4
|
+
|
|
5
|
+
client = Hookbase(api_key="whr_your_api_key")
|
|
6
|
+
|
|
7
|
+
# Create a source to receive GitHub webhooks
|
|
8
|
+
source = client.sources.create({
|
|
9
|
+
"name": "GitHub",
|
|
10
|
+
"slug": "github",
|
|
11
|
+
"provider": "github",
|
|
12
|
+
"verifySignature": True,
|
|
13
|
+
})
|
|
14
|
+
print(f"Source created: {source.id}")
|
|
15
|
+
print(f"Ingest URL: {source.ingest_url}")
|
|
16
|
+
print(f"Signing secret: {source.signing_secret}")
|
|
17
|
+
|
|
18
|
+
# Create a destination to forward webhooks to
|
|
19
|
+
dest = client.destinations.create({
|
|
20
|
+
"name": "My Backend",
|
|
21
|
+
"slug": "backend",
|
|
22
|
+
"url": "https://api.example.com/webhooks",
|
|
23
|
+
"method": "POST",
|
|
24
|
+
"retryCount": 3,
|
|
25
|
+
})
|
|
26
|
+
print(f"Destination created: {dest.id}")
|
|
27
|
+
|
|
28
|
+
# Create a route connecting source to destination
|
|
29
|
+
route = client.routes.create({
|
|
30
|
+
"name": "GitHub to Backend",
|
|
31
|
+
"sourceId": source.id,
|
|
32
|
+
"destinationId": dest.id,
|
|
33
|
+
})
|
|
34
|
+
print(f"Route created: {route.id}")
|
|
35
|
+
|
|
36
|
+
# List recent events
|
|
37
|
+
events = client.events.list(source_id=source.id, limit=10)
|
|
38
|
+
for event in events.data:
|
|
39
|
+
print(f" Event {event.id}: {event.event_type} ({event.status})")
|
|
40
|
+
|
|
41
|
+
client.close()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Error handling patterns."""
|
|
2
|
+
|
|
3
|
+
from hookbase import (
|
|
4
|
+
Hookbase,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
APIError,
|
|
11
|
+
TimeoutError,
|
|
12
|
+
NetworkError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
client = Hookbase(api_key="whr_your_api_key")
|
|
16
|
+
|
|
17
|
+
# --- Handling specific errors ---
|
|
18
|
+
try:
|
|
19
|
+
source = client.sources.get("src_nonexistent")
|
|
20
|
+
except NotFoundError:
|
|
21
|
+
print("Source not found")
|
|
22
|
+
except AuthenticationError:
|
|
23
|
+
print("Invalid API key - check your credentials")
|
|
24
|
+
except ForbiddenError as e:
|
|
25
|
+
print(f"Access denied: {e}")
|
|
26
|
+
except RateLimitError as e:
|
|
27
|
+
print(f"Rate limited - retry after {e.retry_after}s")
|
|
28
|
+
except ValidationError as e:
|
|
29
|
+
print(f"Validation failed: {e}")
|
|
30
|
+
if e.validation_errors:
|
|
31
|
+
for field, errors in e.validation_errors.items():
|
|
32
|
+
print(f" {field}: {', '.join(errors)}")
|
|
33
|
+
|
|
34
|
+
# --- Catching all API errors ---
|
|
35
|
+
try:
|
|
36
|
+
client.sources.create({"name": ""})
|
|
37
|
+
except APIError as e:
|
|
38
|
+
print(f"API error {e.status_code}: {e}")
|
|
39
|
+
print(f" Code: {e.code}")
|
|
40
|
+
print(f" Request ID: {e.request_id}")
|
|
41
|
+
|
|
42
|
+
# --- Network / timeout errors ---
|
|
43
|
+
try:
|
|
44
|
+
client.sources.list()
|
|
45
|
+
except TimeoutError:
|
|
46
|
+
print("Request timed out")
|
|
47
|
+
except NetworkError as e:
|
|
48
|
+
print(f"Network error: {e}")
|
|
49
|
+
if e.cause:
|
|
50
|
+
print(f" Caused by: {e.cause}")
|
|
51
|
+
|
|
52
|
+
client.close()
|