hookbase 2.4.0__tar.gz → 2.6.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.
Files changed (79) hide show
  1. {hookbase-2.4.0 → hookbase-2.6.0}/PKG-INFO +1 -1
  2. hookbase-2.6.0/src/hookbase/_version.py +1 -0
  3. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/__init__.py +6 -0
  4. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/cron_jobs.py +3 -0
  5. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/destinations.py +38 -7
  6. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/test_destinations.py +89 -1
  7. hookbase-2.4.0/src/hookbase/_version.py +0 -1
  8. {hookbase-2.4.0 → hookbase-2.6.0}/.github/workflows/python-publish.yml +0 -0
  9. {hookbase-2.4.0 → hookbase-2.6.0}/.gitignore +0 -0
  10. {hookbase-2.4.0 → hookbase-2.6.0}/LICENSE +0 -0
  11. {hookbase-2.4.0 → hookbase-2.6.0}/README.md +0 -0
  12. {hookbase-2.4.0 → hookbase-2.6.0}/examples/async_usage.py +0 -0
  13. {hookbase-2.4.0 → hookbase-2.6.0}/examples/basic_inbound.py +0 -0
  14. {hookbase-2.4.0 → hookbase-2.6.0}/examples/error_handling.py +0 -0
  15. {hookbase-2.4.0 → hookbase-2.6.0}/examples/send_webhooks.py +0 -0
  16. {hookbase-2.4.0 → hookbase-2.6.0}/examples/verify_webhooks.py +0 -0
  17. {hookbase-2.4.0 → hookbase-2.6.0}/pyproject.toml +0 -0
  18. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/__init__.py +0 -0
  19. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/_client.py +0 -0
  20. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/_constants.py +0 -0
  21. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/_pagination.py +0 -0
  22. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/client.py +0 -0
  23. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/errors.py +0 -0
  24. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/_base.py +0 -0
  25. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/analytics.py +0 -0
  26. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/api_keys.py +0 -0
  27. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/applications.py +0 -0
  28. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/common.py +0 -0
  29. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/deliveries.py +0 -0
  30. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/dlq.py +0 -0
  31. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/endpoints.py +0 -0
  32. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/event_types.py +0 -0
  33. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/events.py +0 -0
  34. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/filters.py +0 -0
  35. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/messages.py +0 -0
  36. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/organizations.py +0 -0
  37. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/portal_tokens.py +0 -0
  38. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/routes.py +0 -0
  39. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/schemas.py +0 -0
  40. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/sources.py +0 -0
  41. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/subscriptions.py +0 -0
  42. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/transforms.py +0 -0
  43. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/models/tunnels.py +0 -0
  44. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/py.typed +0 -0
  45. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/__init__.py +0 -0
  46. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/_base.py +0 -0
  47. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/analytics.py +0 -0
  48. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/api_keys.py +0 -0
  49. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/applications.py +0 -0
  50. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/cron_jobs.py +0 -0
  51. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/deliveries.py +0 -0
  52. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/destinations.py +0 -0
  53. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/dlq.py +0 -0
  54. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/endpoints.py +0 -0
  55. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/event_types.py +0 -0
  56. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/events.py +0 -0
  57. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/filters.py +0 -0
  58. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/message_log.py +0 -0
  59. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/messages.py +0 -0
  60. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/organizations.py +0 -0
  61. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/portal_tokens.py +0 -0
  62. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/routes.py +0 -0
  63. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/schemas.py +0 -0
  64. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/sources.py +0 -0
  65. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/subscriptions.py +0 -0
  66. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/transforms.py +0 -0
  67. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/resources/tunnels.py +0 -0
  68. {hookbase-2.4.0 → hookbase-2.6.0}/src/hookbase/webhook.py +0 -0
  69. {hookbase-2.4.0 → hookbase-2.6.0}/tests/__init__.py +0 -0
  70. {hookbase-2.4.0 → hookbase-2.6.0}/tests/conftest.py +0 -0
  71. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/__init__.py +0 -0
  72. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/test_applications.py +0 -0
  73. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/test_dlq.py +0 -0
  74. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/test_messages.py +0 -0
  75. {hookbase-2.4.0 → hookbase-2.6.0}/tests/resources/test_sources.py +0 -0
  76. {hookbase-2.4.0 → hookbase-2.6.0}/tests/test_client.py +0 -0
  77. {hookbase-2.4.0 → hookbase-2.6.0}/tests/test_errors.py +0 -0
  78. {hookbase-2.4.0 → hookbase-2.6.0}/tests/test_pagination.py +0 -0
  79. {hookbase-2.4.0 → hookbase-2.6.0}/tests/test_webhook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hookbase
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Official Python SDK for the Hookbase webhook management platform
5
5
  Project-URL: Homepage, https://hookbase.app
6
6
  Project-URL: Documentation, https://www.hookbase.app/docs/send/sdk
@@ -0,0 +1 @@
1
+ __version__ = "2.6.0"
@@ -26,8 +26,11 @@ from .destinations import (
26
26
  GCSConfig,
27
27
  HttpMethod,
28
28
  R2Config,
29
+ RateUnit,
29
30
  S3Config,
30
31
  TestResult,
32
+ Throttle,
33
+ ThrottleMode,
31
34
  UpdateDestinationParams,
32
35
  )
33
36
  from .dlq import (
@@ -137,8 +140,11 @@ __all__ = [
137
140
  "GCSConfig",
138
141
  "HttpMethod",
139
142
  "R2Config",
143
+ "RateUnit",
140
144
  "S3Config",
141
145
  "TestResult",
146
+ "Throttle",
147
+ "ThrottleMode",
142
148
  "UpdateDestinationParams",
143
149
  # DLQ
144
150
  "DlqBulkDeleteResult",
@@ -19,6 +19,7 @@ class CronJob(HookbaseModel):
19
19
  schedule: str = ""
20
20
  timezone: str = "UTC"
21
21
  is_active: bool = True
22
+ use_static_ip: bool = True
22
23
  group_id: str | None = None
23
24
  last_run_at: str | None = None
24
25
  next_run_at: str | None = None
@@ -49,6 +50,7 @@ class CreateCronJobParams(HookbaseModel):
49
50
  schedule: str
50
51
  timezone: str | None = None
51
52
  is_active: bool | None = None
53
+ use_static_ip: bool | None = None
52
54
  group_id: str | None = None
53
55
 
54
56
 
@@ -62,6 +64,7 @@ class UpdateCronJobParams(HookbaseModel):
62
64
  schedule: str | None = None
63
65
  timezone: str | None = None
64
66
  is_active: bool | None = None
67
+ use_static_ip: bool | None = None
65
68
  group_id: str | None = None
66
69
 
67
70
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  from typing import Any, Literal
5
5
 
6
- from pydantic import field_validator
6
+ from pydantic import field_validator, model_validator
7
7
 
8
8
  from ._base import HookbaseModel
9
9
 
@@ -13,6 +13,8 @@ DestinationType = Literal["http", "s3", "r2", "gcs", "azure_blob"]
13
13
  FileFormat = Literal["json", "jsonl"]
14
14
  PartitionBy = Literal["date", "hour", "source"]
15
15
  FieldMappingType = Literal["string", "number", "boolean", "timestamp", "json"]
16
+ ThrottleMode = Literal["off", "rate", "concurrency"]
17
+ RateUnit = Literal["second", "minute", "hour"]
16
18
 
17
19
 
18
20
  class FieldMapping(HookbaseModel):
@@ -57,6 +59,14 @@ class AzureBlobConfig(HookbaseModel):
57
59
  partition_by: PartitionBy | None = None
58
60
 
59
61
 
62
+ class Throttle(HookbaseModel):
63
+ mode: ThrottleMode = "off"
64
+ rate_limit: int | None = None
65
+ rate_unit: RateUnit | None = None
66
+ max_concurrency: int | None = None
67
+ queue_limit: int | None = None
68
+
69
+
60
70
  class Destination(HookbaseModel):
61
71
  id: str
62
72
  organization_id: str | None = None
@@ -72,8 +82,7 @@ class Destination(HookbaseModel):
72
82
  timeout: int = 30
73
83
  retry_count: int = 3
74
84
  retry_interval: int = 60
75
- rate_limit: int | None = None
76
- rate_limit_window: int | None = None
85
+ throttle: Throttle | None = None
77
86
  is_active: bool = True
78
87
  use_static_ip: bool = True
79
88
  config: dict[str, Any] | None = None
@@ -85,6 +94,30 @@ class Destination(HookbaseModel):
85
94
  created_at: str = ""
86
95
  updated_at: str = ""
87
96
 
97
+ @model_validator(mode="before")
98
+ @classmethod
99
+ def _normalize_throttle(cls, data: Any) -> Any:
100
+ """Normalize the two response shapes the API uses for throttle data.
101
+
102
+ POST /api/destinations (create) and GET /api/destinations/export nest
103
+ throttle fields under a `throttle` object, but GET /api/destinations
104
+ (list) and GET /api/destinations/:id return them as flat top-level
105
+ `throttleMode`/`throttleRateLimit`/`throttleRateUnit`/
106
+ `throttleMaxConcurrency`/`throttleQueueLimit` fields (spread directly
107
+ from the DB row). Fold the flat shape into a nested `throttle` dict
108
+ so both response shapes populate the same field.
109
+ """
110
+ if isinstance(data, dict) and "throttle" not in data and "throttleMode" in data:
111
+ data = dict(data)
112
+ data["throttle"] = {
113
+ "mode": data.pop("throttleMode", None) or "off",
114
+ "rateLimit": data.pop("throttleRateLimit", None),
115
+ "rateUnit": data.pop("throttleRateUnit", None),
116
+ "maxConcurrency": data.pop("throttleMaxConcurrency", None),
117
+ "queueLimit": data.pop("throttleQueueLimit", None),
118
+ }
119
+ return data
120
+
88
121
  @field_validator("headers", mode="before")
89
122
  @classmethod
90
123
  def parse_headers(cls, v: Any) -> Any:
@@ -146,8 +179,7 @@ class CreateDestinationParams(HookbaseModel):
146
179
  timeout: int | None = None
147
180
  retry_count: int | None = None
148
181
  retry_interval: int | None = None
149
- rate_limit: int | None = None
150
- rate_limit_window: int | None = None
182
+ throttle: Throttle | None = None
151
183
  config: dict[str, Any] | None = None
152
184
  field_mapping: list[FieldMapping] | None = None
153
185
  use_static_ip: bool | None = None
@@ -166,8 +198,7 @@ class UpdateDestinationParams(HookbaseModel):
166
198
  timeout: int | None = None
167
199
  retry_count: int | None = None
168
200
  retry_interval: int | None = None
169
- rate_limit: int | None = None
170
- rate_limit_window: int | None = None
201
+ throttle: Throttle | None = None
171
202
  is_active: bool | None = None
172
203
  config: dict[str, Any] | None = None
173
204
  field_mapping: list[FieldMapping] | None = None
@@ -4,7 +4,14 @@ import pytest
4
4
  import respx
5
5
 
6
6
  from hookbase import Hookbase
7
- from hookbase.models import Destination, FieldMapping, TestResult
7
+ from hookbase.models import (
8
+ CreateDestinationParams,
9
+ Destination,
10
+ FieldMapping,
11
+ TestResult,
12
+ Throttle,
13
+ UpdateDestinationParams,
14
+ )
8
15
 
9
16
  from ..conftest import make_paginated_response
10
17
 
@@ -183,6 +190,87 @@ def test_destination_without_type_defaults():
183
190
  assert dest.field_mapping is None
184
191
 
185
192
 
193
+ def test_throttle_model():
194
+ throttle = Throttle(mode="rate", rate_limit=10, rate_unit="minute")
195
+ assert throttle.mode == "rate"
196
+ assert throttle.rate_limit == 10
197
+ assert throttle.rate_unit == "minute"
198
+ assert throttle.max_concurrency is None
199
+ assert throttle.queue_limit is None
200
+
201
+ default_throttle = Throttle()
202
+ assert default_throttle.mode == "off"
203
+
204
+
205
+ def test_create_destination_response_nests_throttle(mock_api, client):
206
+ """POST /api/destinations nests throttle fields under `throttle`."""
207
+ data = {
208
+ **DEST_DATA,
209
+ "throttle": {
210
+ "mode": "rate",
211
+ "rateLimit": 100,
212
+ "rateUnit": "minute",
213
+ "maxConcurrency": None,
214
+ "queueLimit": 50,
215
+ },
216
+ }
217
+ mock_api.post("/api/destinations").respond(200, json={"destination": data})
218
+ dest = client.destinations.create({"name": "Backend", "url": "https://example.com/webhooks"})
219
+ assert dest.throttle is not None
220
+ assert dest.throttle.mode == "rate"
221
+ assert dest.throttle.rate_limit == 100
222
+ assert dest.throttle.rate_unit == "minute"
223
+ assert dest.throttle.max_concurrency is None
224
+ assert dest.throttle.queue_limit == 50
225
+
226
+
227
+ def test_get_destination_response_has_flat_throttle_fields(mock_api, client):
228
+ """GET /api/destinations/:id returns throttle fields flat (spread from the
229
+ DB row) rather than nested under `throttle`; the model must normalize
230
+ this into the same `throttle` field used by the create/export shape."""
231
+ data = {
232
+ **DEST_DATA,
233
+ "throttleMode": "concurrency",
234
+ "throttleRateLimit": None,
235
+ "throttleRateUnit": None,
236
+ "throttleMaxConcurrency": 5,
237
+ "throttleQueueLimit": None,
238
+ }
239
+ mock_api.get("/api/destinations/dst_1").respond(200, json={"destination": data})
240
+ dest = client.destinations.get("dst_1")
241
+ assert dest.throttle is not None
242
+ assert dest.throttle.mode == "concurrency"
243
+ assert dest.throttle.max_concurrency == 5
244
+ assert dest.throttle.rate_limit is None
245
+
246
+
247
+ def test_destination_without_throttle_data():
248
+ dest = Destination(**DEST_DATA)
249
+ assert dest.throttle is None
250
+
251
+
252
+ def test_create_destination_params_throttle_serializes_camelcase():
253
+ params = CreateDestinationParams(
254
+ name="Backend",
255
+ url="https://example.com/webhooks",
256
+ throttle=Throttle(mode="rate", rate_limit=10, rate_unit="minute"),
257
+ )
258
+ body = params.model_dump(by_alias=True, exclude_none=True)
259
+ assert body["throttle"] == {"mode": "rate", "rateLimit": 10, "rateUnit": "minute"}
260
+
261
+
262
+ def test_update_destination_params_throttle_serializes_camelcase():
263
+ params = UpdateDestinationParams(
264
+ throttle=Throttle(mode="concurrency", max_concurrency=25, queue_limit=100),
265
+ )
266
+ body = params.model_dump(by_alias=True, exclude_none=True)
267
+ assert body["throttle"] == {
268
+ "mode": "concurrency",
269
+ "maxConcurrency": 25,
270
+ "queueLimit": 100,
271
+ }
272
+
273
+
186
274
  def test_test_destination(mock_api, client):
187
275
  mock_api.post("/api/destinations/dst_1/test").respond(200, json={
188
276
  "success": True, "statusCode": 200, "duration": 150.5,
@@ -1 +0,0 @@
1
- __version__ = "2.4.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes