hookbase 2.3.0__tar.gz → 2.5.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-2.3.0 → hookbase-2.5.0}/PKG-INFO +2 -2
- {hookbase-2.3.0 → hookbase-2.5.0}/pyproject.toml +1 -1
- hookbase-2.5.0/src/hookbase/_version.py +1 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/cron_jobs.py +3 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/destinations.py +6 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/sources.py +23 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/test_sources.py +46 -0
- hookbase-2.3.0/src/hookbase/_version.py +0 -1
- {hookbase-2.3.0 → hookbase-2.5.0}/.github/workflows/python-publish.yml +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/.gitignore +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/LICENSE +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/README.md +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/examples/async_usage.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/examples/basic_inbound.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/examples/error_handling.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/examples/send_webhooks.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/examples/verify_webhooks.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/__init__.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/_client.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/_constants.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/_pagination.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/client.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/errors.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/__init__.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/_base.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/analytics.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/api_keys.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/applications.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/common.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/deliveries.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/dlq.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/endpoints.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/event_types.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/events.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/filters.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/messages.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/organizations.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/portal_tokens.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/routes.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/schemas.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/subscriptions.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/transforms.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/models/tunnels.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/py.typed +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/__init__.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/_base.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/analytics.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/api_keys.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/applications.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/cron_jobs.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/deliveries.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/destinations.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/dlq.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/endpoints.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/event_types.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/events.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/filters.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/message_log.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/messages.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/organizations.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/portal_tokens.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/routes.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/schemas.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/sources.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/subscriptions.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/transforms.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/resources/tunnels.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/src/hookbase/webhook.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/__init__.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/conftest.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/__init__.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/test_applications.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/test_destinations.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/test_dlq.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/resources/test_messages.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/test_client.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/test_errors.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/test_pagination.py +0 -0
- {hookbase-2.3.0 → hookbase-2.5.0}/tests/test_webhook.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hookbase
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Official Python SDK for the Hookbase webhook management platform
|
|
5
5
|
Project-URL: Homepage, https://hookbase.app
|
|
6
|
-
Project-URL: Documentation, https://
|
|
6
|
+
Project-URL: Documentation, https://www.hookbase.app/docs/send/sdk
|
|
7
7
|
Project-URL: Repository, https://github.com/hookbase/hookbase-python
|
|
8
8
|
Project-URL: Issues, https://github.com/hookbase/hookbase-python/issues
|
|
9
9
|
Author-email: Hookbase <support@hookbase.app>
|
|
@@ -43,7 +43,7 @@ dev = [
|
|
|
43
43
|
|
|
44
44
|
[project.urls]
|
|
45
45
|
Homepage = "https://hookbase.app"
|
|
46
|
-
Documentation = "https://
|
|
46
|
+
Documentation = "https://www.hookbase.app/docs/send/sdk"
|
|
47
47
|
Repository = "https://github.com/hookbase/hookbase-python"
|
|
48
48
|
Issues = "https://github.com/hookbase/hookbase-python/issues"
|
|
49
49
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.5.0"
|
|
@@ -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
|
|
|
@@ -78,6 +78,8 @@ class Destination(HookbaseModel):
|
|
|
78
78
|
use_static_ip: bool = True
|
|
79
79
|
config: dict[str, Any] | None = None
|
|
80
80
|
field_mapping: list[FieldMapping] | None = None
|
|
81
|
+
batch_size: int | None = None
|
|
82
|
+
batch_window_seconds: int | None = None
|
|
81
83
|
delivery_count: int = 0
|
|
82
84
|
last_delivery_at: str | None = None
|
|
83
85
|
created_at: str = ""
|
|
@@ -149,6 +151,8 @@ class CreateDestinationParams(HookbaseModel):
|
|
|
149
151
|
config: dict[str, Any] | None = None
|
|
150
152
|
field_mapping: list[FieldMapping] | None = None
|
|
151
153
|
use_static_ip: bool | None = None
|
|
154
|
+
batch_size: int | None = None
|
|
155
|
+
batch_window_seconds: int | None = None
|
|
152
156
|
|
|
153
157
|
|
|
154
158
|
class UpdateDestinationParams(HookbaseModel):
|
|
@@ -168,6 +172,8 @@ class UpdateDestinationParams(HookbaseModel):
|
|
|
168
172
|
config: dict[str, Any] | None = None
|
|
169
173
|
field_mapping: list[FieldMapping] | None = None
|
|
170
174
|
use_static_ip: bool | None = None
|
|
175
|
+
batch_size: int | None = None
|
|
176
|
+
batch_window_seconds: int | None = None
|
|
171
177
|
|
|
172
178
|
|
|
173
179
|
class TestResult(HookbaseModel):
|
|
@@ -13,6 +13,9 @@ SourceProvider = Literal[
|
|
|
13
13
|
]
|
|
14
14
|
DedupStrategy = Literal["none", "header", "payload_hash", "event_id"]
|
|
15
15
|
IpFilterMode = Literal["none", "allowlist", "denylist"]
|
|
16
|
+
# HTTP verbs an ingest endpoint can be restricted to. OPTIONS is excluded: CORS preflight is
|
|
17
|
+
# answered before ingest runs, so it is never gateable.
|
|
18
|
+
IngestMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class Source(HookbaseModel):
|
|
@@ -35,6 +38,8 @@ class Source(HookbaseModel):
|
|
|
35
38
|
rate_limit: int | None = None
|
|
36
39
|
rate_limit_window: int | None = None
|
|
37
40
|
transient_mode: bool = False
|
|
41
|
+
#: HTTP verbs the ingest endpoint accepts. Empty list means any method.
|
|
42
|
+
allowed_methods: list[str] = []
|
|
38
43
|
event_count: int = 0
|
|
39
44
|
last_event_at: str | None = None
|
|
40
45
|
created_at: str = ""
|
|
@@ -58,6 +63,20 @@ class Source(HookbaseModel):
|
|
|
58
63
|
return None
|
|
59
64
|
return v
|
|
60
65
|
|
|
66
|
+
@field_validator("allowed_methods", mode="before")
|
|
67
|
+
@classmethod
|
|
68
|
+
def parse_allowed_methods(cls, v: Any) -> Any:
|
|
69
|
+
"""Coerce null/JSON-string forms to a list; both mean "any method" when empty."""
|
|
70
|
+
if v is None:
|
|
71
|
+
return []
|
|
72
|
+
if isinstance(v, str):
|
|
73
|
+
try:
|
|
74
|
+
parsed = json.loads(v)
|
|
75
|
+
return parsed if isinstance(parsed, list) else []
|
|
76
|
+
except (json.JSONDecodeError, ValueError):
|
|
77
|
+
return []
|
|
78
|
+
return v
|
|
79
|
+
|
|
61
80
|
|
|
62
81
|
class SourceWithSecret(Source):
|
|
63
82
|
signing_secret: str = ""
|
|
@@ -78,6 +97,8 @@ class CreateSourceParams(HookbaseModel):
|
|
|
78
97
|
rate_limit: int | None = None
|
|
79
98
|
rate_limit_window: int | None = None
|
|
80
99
|
transient_mode: bool | None = None
|
|
100
|
+
#: Restrict the ingest endpoint to these verbs. None/[] accepts any method.
|
|
101
|
+
allowed_methods: list[IngestMethod] | None = None
|
|
81
102
|
|
|
82
103
|
|
|
83
104
|
class UpdateSourceParams(HookbaseModel):
|
|
@@ -94,3 +115,5 @@ class UpdateSourceParams(HookbaseModel):
|
|
|
94
115
|
rate_limit: int | None = None
|
|
95
116
|
rate_limit_window: int | None = None
|
|
96
117
|
transient_mode: bool | None = None
|
|
118
|
+
#: Restrict the ingest endpoint to these verbs. Pass [] to accept any method again.
|
|
119
|
+
allowed_methods: list[IngestMethod] | None = None
|
|
@@ -189,3 +189,49 @@ def test_list_sources_with_transient_mode(mock_api, client):
|
|
|
189
189
|
))
|
|
190
190
|
page = client.sources.list()
|
|
191
191
|
assert page.data[0].transient_mode is True
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# --- allowed_methods tests ---
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_source_allowed_methods_defaults_empty():
|
|
198
|
+
"""No restriction is the default; [] means the endpoint accepts any HTTP method."""
|
|
199
|
+
src = Source(**SOURCE_DATA)
|
|
200
|
+
assert src.allowed_methods == []
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_source_allowed_methods_from_list():
|
|
204
|
+
data = {**SOURCE_DATA, "allowedMethods": ["GET", "POST"]}
|
|
205
|
+
src = Source(**data)
|
|
206
|
+
assert src.allowed_methods == ["GET", "POST"]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_source_allowed_methods_from_json_string():
|
|
210
|
+
"""The column is JSONB projected as text, so it can arrive as a JSON string."""
|
|
211
|
+
data = {**SOURCE_DATA, "allowedMethods": '["POST"]'}
|
|
212
|
+
src = Source(**data)
|
|
213
|
+
assert src.allowed_methods == ["POST"]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_source_allowed_methods_null_becomes_empty():
|
|
217
|
+
"""NULL and [] both mean "any method" — normalize so callers only handle a list."""
|
|
218
|
+
data = {**SOURCE_DATA, "allowedMethods": None}
|
|
219
|
+
src = Source(**data)
|
|
220
|
+
assert src.allowed_methods == []
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_source_allowed_methods_malformed_fails_open():
|
|
224
|
+
data = {**SOURCE_DATA, "allowedMethods": "not json"}
|
|
225
|
+
src = Source(**data)
|
|
226
|
+
assert src.allowed_methods == []
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_create_source_params_serializes_allowed_methods():
|
|
230
|
+
params = CreateSourceParams(name="s", allowed_methods=["GET", "POST"])
|
|
231
|
+
assert params.model_dump(by_alias=True, exclude_none=True)["allowedMethods"] == ["GET", "POST"]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_update_source_params_empty_list_is_sent():
|
|
235
|
+
"""[] must survive serialization — it is how a source reverts to accepting any method."""
|
|
236
|
+
params = UpdateSourceParams(allowed_methods=[])
|
|
237
|
+
assert params.model_dump(by_alias=True, exclude_none=True)["allowedMethods"] == []
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.3.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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|