hookbase 1.0.0__tar.gz → 2.1.1__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.1.1/.github/workflows/python-publish.yml +86 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/PKG-INFO +2 -1
- {hookbase-1.0.0 → hookbase-2.1.1}/pyproject.toml +1 -0
- hookbase-2.1.1/src/hookbase/_version.py +1 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/client.py +6 -2
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/deliveries.py +4 -2
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/destinations.py +30 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/events.py +25 -2
- hookbase-2.1.1/src/hookbase/models/portal_tokens.py +23 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/sources.py +25 -1
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/transforms.py +1 -1
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/__init__.py +2 -0
- hookbase-2.1.1/src/hookbase/resources/portal_tokens.py +60 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/test_sources.py +79 -0
- hookbase-1.0.0/src/hookbase/_version.py +0 -1
- {hookbase-1.0.0 → hookbase-2.1.1}/.gitignore +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/LICENSE +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/README.md +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/examples/async_usage.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/examples/basic_inbound.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/examples/error_handling.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/examples/send_webhooks.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/examples/verify_webhooks.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/__init__.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/_client.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/_constants.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/_pagination.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/errors.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/__init__.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/_base.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/analytics.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/api_keys.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/applications.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/common.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/cron_jobs.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/dlq.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/endpoints.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/event_types.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/filters.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/messages.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/organizations.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/routes.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/schemas.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/subscriptions.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/models/tunnels.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/py.typed +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/_base.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/analytics.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/api_keys.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/applications.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/cron_jobs.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/deliveries.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/destinations.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/dlq.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/endpoints.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/event_types.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/events.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/filters.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/message_log.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/messages.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/organizations.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/routes.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/schemas.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/sources.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/subscriptions.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/transforms.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/resources/tunnels.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/src/hookbase/webhook.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/__init__.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/conftest.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/__init__.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/test_applications.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/test_destinations.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/test_dlq.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/resources/test_messages.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/test_client.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/test_errors.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/test_pagination.py +0 -0
- {hookbase-1.0.0 → hookbase-2.1.1}/tests/test_webhook.py +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: CI / Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: |
|
|
30
|
+
python -m pip install --upgrade pip
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Lint with ruff
|
|
34
|
+
run: ruff check src/ tests/
|
|
35
|
+
|
|
36
|
+
- name: Type check with mypy
|
|
37
|
+
run: mypy src/hookbase
|
|
38
|
+
|
|
39
|
+
- name: Run tests
|
|
40
|
+
run: pytest -v
|
|
41
|
+
|
|
42
|
+
release-build:
|
|
43
|
+
if: github.event_name == 'release'
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs: test
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.x"
|
|
53
|
+
|
|
54
|
+
- name: Build release distributions
|
|
55
|
+
run: |
|
|
56
|
+
python -m pip install build
|
|
57
|
+
python -m build
|
|
58
|
+
|
|
59
|
+
- name: Upload distributions
|
|
60
|
+
uses: actions/upload-artifact@v4
|
|
61
|
+
with:
|
|
62
|
+
name: release-dists
|
|
63
|
+
path: dist/
|
|
64
|
+
|
|
65
|
+
pypi-publish:
|
|
66
|
+
if: github.event_name == 'release'
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
needs: release-build
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write
|
|
71
|
+
|
|
72
|
+
environment:
|
|
73
|
+
name: pypi
|
|
74
|
+
url: https://pypi.org/project/hookbase/${{ github.event.release.tag_name }}
|
|
75
|
+
|
|
76
|
+
steps:
|
|
77
|
+
- name: Retrieve release distributions
|
|
78
|
+
uses: actions/download-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: release-dists
|
|
81
|
+
path: dist/
|
|
82
|
+
|
|
83
|
+
- name: Publish release distributions to PyPI
|
|
84
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
85
|
+
with:
|
|
86
|
+
packages-dir: dist/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hookbase
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.1
|
|
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://docs.hookbase.app/sdk/python
|
|
@@ -22,6 +22,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: eval-type-backport>=0.2.0; python_version < '3.10'
|
|
25
26
|
Requires-Dist: httpx<1.0.0,>=0.25.0
|
|
26
27
|
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
27
28
|
Provides-Extra: dev
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.1"
|
|
@@ -25,6 +25,7 @@ from .resources import (
|
|
|
25
25
|
AsyncMessageLog,
|
|
26
26
|
AsyncMessages,
|
|
27
27
|
AsyncOrganizations,
|
|
28
|
+
AsyncPortalTokens,
|
|
28
29
|
AsyncRoutes,
|
|
29
30
|
AsyncSchemas,
|
|
30
31
|
AsyncSources,
|
|
@@ -41,6 +42,7 @@ from .resources import (
|
|
|
41
42
|
MessageLog,
|
|
42
43
|
Messages,
|
|
43
44
|
Organizations,
|
|
45
|
+
PortalTokens,
|
|
44
46
|
Routes,
|
|
45
47
|
Schemas,
|
|
46
48
|
Sources,
|
|
@@ -55,7 +57,7 @@ class _OutboundNamespace:
|
|
|
55
57
|
|
|
56
58
|
__slots__ = (
|
|
57
59
|
"applications", "endpoints", "event_types", "subscriptions",
|
|
58
|
-
"messages", "message_log", "dlq",
|
|
60
|
+
"messages", "message_log", "portal_tokens", "dlq",
|
|
59
61
|
)
|
|
60
62
|
|
|
61
63
|
def __init__(self, transport: SyncTransport) -> None:
|
|
@@ -65,6 +67,7 @@ class _OutboundNamespace:
|
|
|
65
67
|
self.subscriptions = Subscriptions(transport)
|
|
66
68
|
self.messages = Messages(transport)
|
|
67
69
|
self.message_log = MessageLog(transport)
|
|
70
|
+
self.portal_tokens = PortalTokens(transport)
|
|
68
71
|
self.dlq = DLQ(transport)
|
|
69
72
|
|
|
70
73
|
|
|
@@ -73,7 +76,7 @@ class _AsyncOutboundNamespace:
|
|
|
73
76
|
|
|
74
77
|
__slots__ = (
|
|
75
78
|
"applications", "endpoints", "event_types", "subscriptions",
|
|
76
|
-
"messages", "message_log", "dlq",
|
|
79
|
+
"messages", "message_log", "portal_tokens", "dlq",
|
|
77
80
|
)
|
|
78
81
|
|
|
79
82
|
def __init__(self, transport: AsyncTransport) -> None:
|
|
@@ -83,6 +86,7 @@ class _AsyncOutboundNamespace:
|
|
|
83
86
|
self.subscriptions = AsyncSubscriptions(transport)
|
|
84
87
|
self.messages = AsyncMessages(transport)
|
|
85
88
|
self.message_log = AsyncMessageLog(transport)
|
|
89
|
+
self.portal_tokens = AsyncPortalTokens(transport)
|
|
86
90
|
self.dlq = AsyncDLQ(transport)
|
|
87
91
|
|
|
88
92
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from ._base import HookbaseModel
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# API may return: pending, success, failed, retrying, delivered,
|
|
8
|
+
# failed_over, schema_failed, exhausted, etc.
|
|
9
|
+
DeliveryStatus = str
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class Delivery(HookbaseModel):
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Any, Literal
|
|
4
5
|
|
|
6
|
+
from pydantic import field_validator
|
|
7
|
+
|
|
5
8
|
from ._base import HookbaseModel
|
|
6
9
|
|
|
7
10
|
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|
@@ -30,6 +33,33 @@ class Destination(HookbaseModel):
|
|
|
30
33
|
created_at: str = ""
|
|
31
34
|
updated_at: str = ""
|
|
32
35
|
|
|
36
|
+
@field_validator("headers", mode="before")
|
|
37
|
+
@classmethod
|
|
38
|
+
def parse_headers(cls, v: Any) -> Any:
|
|
39
|
+
if isinstance(v, str):
|
|
40
|
+
try:
|
|
41
|
+
return json.loads(v)
|
|
42
|
+
except (json.JSONDecodeError, ValueError):
|
|
43
|
+
return None
|
|
44
|
+
return v
|
|
45
|
+
|
|
46
|
+
@field_validator("auth_config", mode="before")
|
|
47
|
+
@classmethod
|
|
48
|
+
def parse_auth_config(cls, v: Any) -> Any:
|
|
49
|
+
if isinstance(v, str):
|
|
50
|
+
try:
|
|
51
|
+
return json.loads(v)
|
|
52
|
+
except (json.JSONDecodeError, ValueError):
|
|
53
|
+
return None
|
|
54
|
+
return v
|
|
55
|
+
|
|
56
|
+
@field_validator("is_active", mode="before")
|
|
57
|
+
@classmethod
|
|
58
|
+
def parse_is_active(cls, v: Any) -> Any:
|
|
59
|
+
if isinstance(v, int):
|
|
60
|
+
return bool(v)
|
|
61
|
+
return v
|
|
62
|
+
|
|
33
63
|
|
|
34
64
|
class CreateDestinationParams(HookbaseModel):
|
|
35
65
|
name: str
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import field_validator
|
|
4
7
|
|
|
5
8
|
from ._base import HookbaseModel
|
|
6
9
|
|
|
7
|
-
InboundEventStatus =
|
|
10
|
+
InboundEventStatus = str # API may return: delivered, failed, pending, partial, schema_failed, etc.
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class DeliveryStats(HookbaseModel):
|
|
@@ -53,6 +56,16 @@ class EventDetail(HookbaseModel):
|
|
|
53
56
|
source_name: str = ""
|
|
54
57
|
deliveries: list[EventDelivery] = []
|
|
55
58
|
|
|
59
|
+
@field_validator("headers", mode="before")
|
|
60
|
+
@classmethod
|
|
61
|
+
def parse_headers(cls, v: Any) -> Any:
|
|
62
|
+
if isinstance(v, str):
|
|
63
|
+
try:
|
|
64
|
+
return json.loads(v)
|
|
65
|
+
except (json.JSONDecodeError, ValueError):
|
|
66
|
+
return {}
|
|
67
|
+
return v if v is not None else {}
|
|
68
|
+
|
|
56
69
|
|
|
57
70
|
class EventDebugEvent(HookbaseModel):
|
|
58
71
|
id: str
|
|
@@ -64,6 +77,16 @@ class EventDebugEvent(HookbaseModel):
|
|
|
64
77
|
received_at: str = ""
|
|
65
78
|
ip_address: str | None = None
|
|
66
79
|
|
|
80
|
+
@field_validator("headers", mode="before")
|
|
81
|
+
@classmethod
|
|
82
|
+
def parse_headers(cls, v: Any) -> Any:
|
|
83
|
+
if isinstance(v, str):
|
|
84
|
+
try:
|
|
85
|
+
return json.loads(v)
|
|
86
|
+
except (json.JSONDecodeError, ValueError):
|
|
87
|
+
return {}
|
|
88
|
+
return v if v is not None else {}
|
|
89
|
+
|
|
67
90
|
|
|
68
91
|
class EventDebugInfo(HookbaseModel):
|
|
69
92
|
event: EventDebugEvent
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._base import HookbaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PortalToken(HookbaseModel):
|
|
7
|
+
id: str
|
|
8
|
+
application_id: str = ""
|
|
9
|
+
token: str | None = None
|
|
10
|
+
token_prefix: str | None = None
|
|
11
|
+
name: str | None = None
|
|
12
|
+
scopes: list[str] = []
|
|
13
|
+
expires_at: str = ""
|
|
14
|
+
created_at: str = ""
|
|
15
|
+
is_expired: bool | None = None
|
|
16
|
+
is_revoked: bool | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CreatePortalTokenParams(HookbaseModel):
|
|
20
|
+
name: str | None = None
|
|
21
|
+
scopes: list[str] | None = None
|
|
22
|
+
expires_in_days: int | None = None
|
|
23
|
+
allowed_ips: list[str] | None = None
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import field_validator
|
|
4
7
|
|
|
5
8
|
from ._base import HookbaseModel
|
|
6
9
|
|
|
@@ -31,11 +34,30 @@ class Source(HookbaseModel):
|
|
|
31
34
|
ip_denylist: list[str] | None = None
|
|
32
35
|
rate_limit: int | None = None
|
|
33
36
|
rate_limit_window: int | None = None
|
|
37
|
+
transient_mode: bool = False
|
|
34
38
|
event_count: int = 0
|
|
35
39
|
last_event_at: str | None = None
|
|
36
40
|
created_at: str = ""
|
|
37
41
|
updated_at: str = ""
|
|
38
42
|
|
|
43
|
+
@field_validator("is_active", "verify_signature", "transient_mode", mode="before")
|
|
44
|
+
@classmethod
|
|
45
|
+
def parse_bool_from_int(cls, v: Any) -> Any:
|
|
46
|
+
if isinstance(v, int):
|
|
47
|
+
return bool(v)
|
|
48
|
+
return v
|
|
49
|
+
|
|
50
|
+
@field_validator("ip_allowlist", "ip_denylist", mode="before")
|
|
51
|
+
@classmethod
|
|
52
|
+
def parse_json_list(cls, v: Any) -> Any:
|
|
53
|
+
if isinstance(v, str):
|
|
54
|
+
try:
|
|
55
|
+
parsed = json.loads(v)
|
|
56
|
+
return parsed if isinstance(parsed, list) else None
|
|
57
|
+
except (json.JSONDecodeError, ValueError):
|
|
58
|
+
return None
|
|
59
|
+
return v
|
|
60
|
+
|
|
39
61
|
|
|
40
62
|
class SourceWithSecret(Source):
|
|
41
63
|
signing_secret: str = ""
|
|
@@ -55,6 +77,7 @@ class CreateSourceParams(HookbaseModel):
|
|
|
55
77
|
ip_denylist: list[str] | None = None
|
|
56
78
|
rate_limit: int | None = None
|
|
57
79
|
rate_limit_window: int | None = None
|
|
80
|
+
transient_mode: bool | None = None
|
|
58
81
|
|
|
59
82
|
|
|
60
83
|
class UpdateSourceParams(HookbaseModel):
|
|
@@ -70,3 +93,4 @@ class UpdateSourceParams(HookbaseModel):
|
|
|
70
93
|
ip_denylist: list[str] | None = None
|
|
71
94
|
rate_limit: int | None = None
|
|
72
95
|
rate_limit_window: int | None = None
|
|
96
|
+
transient_mode: bool | None = None
|
|
@@ -4,7 +4,7 @@ from typing import Any, Literal
|
|
|
4
4
|
|
|
5
5
|
from ._base import HookbaseModel
|
|
6
6
|
|
|
7
|
-
TransformType = Literal["jsonata", "javascript", "mapping"]
|
|
7
|
+
TransformType = Literal["jsonata", "javascript", "mapping", "liquid", "xslt"]
|
|
8
8
|
ContentFormat = Literal["json", "xml", "form", "text"]
|
|
9
9
|
|
|
10
10
|
|
|
@@ -12,6 +12,7 @@ from .filters import AsyncFilters, Filters
|
|
|
12
12
|
from .message_log import AsyncMessageLog, MessageLog
|
|
13
13
|
from .messages import AsyncMessages, Messages
|
|
14
14
|
from .organizations import AsyncOrganizations, Organizations
|
|
15
|
+
from .portal_tokens import AsyncPortalTokens, PortalTokens
|
|
15
16
|
from .routes import AsyncRoutes, Routes
|
|
16
17
|
from .schemas import AsyncSchemas, Schemas
|
|
17
18
|
from .sources import AsyncSources, Sources
|
|
@@ -34,6 +35,7 @@ __all__ = [
|
|
|
34
35
|
"MessageLog", "AsyncMessageLog",
|
|
35
36
|
"Messages", "AsyncMessages",
|
|
36
37
|
"Organizations", "AsyncOrganizations",
|
|
38
|
+
"PortalTokens", "AsyncPortalTokens",
|
|
37
39
|
"Routes", "AsyncRoutes",
|
|
38
40
|
"Schemas", "AsyncSchemas",
|
|
39
41
|
"Sources", "AsyncSources",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..models.portal_tokens import CreatePortalTokenParams, PortalToken
|
|
6
|
+
from ._base import AsyncResource, SyncResource, _to_body
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PortalTokens(SyncResource):
|
|
10
|
+
def create(
|
|
11
|
+
self,
|
|
12
|
+
application_id: str,
|
|
13
|
+
params: CreatePortalTokenParams | dict[str, Any] | None = None,
|
|
14
|
+
) -> PortalToken:
|
|
15
|
+
body = _to_body(params) if params else {}
|
|
16
|
+
resp = self._request(
|
|
17
|
+
"POST",
|
|
18
|
+
f"/api/portal/webhook-applications/{application_id}/tokens",
|
|
19
|
+
json=body,
|
|
20
|
+
)
|
|
21
|
+
data = resp.get("data", resp)
|
|
22
|
+
return self._parse(PortalToken, data)
|
|
23
|
+
|
|
24
|
+
def list(self, application_id: str) -> list[PortalToken]:
|
|
25
|
+
resp = self._request(
|
|
26
|
+
"GET",
|
|
27
|
+
f"/api/portal/webhook-applications/{application_id}/tokens",
|
|
28
|
+
)
|
|
29
|
+
items = resp.get("data", [])
|
|
30
|
+
return self._parse_list(PortalToken, items if isinstance(items, list) else [])
|
|
31
|
+
|
|
32
|
+
def revoke(self, token_id: str) -> None:
|
|
33
|
+
self._request("DELETE", f"/api/portal/tokens/{token_id}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncPortalTokens(AsyncResource):
|
|
37
|
+
async def create(
|
|
38
|
+
self,
|
|
39
|
+
application_id: str,
|
|
40
|
+
params: CreatePortalTokenParams | dict[str, Any] | None = None,
|
|
41
|
+
) -> PortalToken:
|
|
42
|
+
body = _to_body(params) if params else {}
|
|
43
|
+
resp = await self._request(
|
|
44
|
+
"POST",
|
|
45
|
+
f"/api/portal/webhook-applications/{application_id}/tokens",
|
|
46
|
+
json=body,
|
|
47
|
+
)
|
|
48
|
+
data = resp.get("data", resp)
|
|
49
|
+
return self._parse(PortalToken, data)
|
|
50
|
+
|
|
51
|
+
async def list(self, application_id: str) -> list[PortalToken]:
|
|
52
|
+
resp = await self._request(
|
|
53
|
+
"GET",
|
|
54
|
+
f"/api/portal/webhook-applications/{application_id}/tokens",
|
|
55
|
+
)
|
|
56
|
+
items = resp.get("data", [])
|
|
57
|
+
return self._parse_list(PortalToken, items if isinstance(items, list) else [])
|
|
58
|
+
|
|
59
|
+
async def revoke(self, token_id: str) -> None:
|
|
60
|
+
await self._request("DELETE", f"/api/portal/tokens/{token_id}")
|
|
@@ -5,6 +5,7 @@ import respx
|
|
|
5
5
|
|
|
6
6
|
from hookbase import Hookbase
|
|
7
7
|
from hookbase.models import Source, SourceWithSecret
|
|
8
|
+
from hookbase.models.sources import CreateSourceParams, UpdateSourceParams
|
|
8
9
|
|
|
9
10
|
from ..conftest import make_paginated_response
|
|
10
11
|
|
|
@@ -110,3 +111,81 @@ def test_import_sources(mock_api, client):
|
|
|
110
111
|
})
|
|
111
112
|
result = client.sources.import_sources([SOURCE_DATA])
|
|
112
113
|
assert result.imported == 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# --- transient_mode tests ---
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_source_transient_mode_defaults_false():
|
|
120
|
+
src = Source(**SOURCE_DATA)
|
|
121
|
+
assert src.transient_mode is False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_source_transient_mode_true():
|
|
125
|
+
data = {**SOURCE_DATA, "transientMode": True}
|
|
126
|
+
src = Source(**data)
|
|
127
|
+
assert src.transient_mode is True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_source_transient_mode_from_int():
|
|
131
|
+
"""D1/SQLite returns booleans as 0/1 integers."""
|
|
132
|
+
data = {**SOURCE_DATA, "transientMode": 1}
|
|
133
|
+
src = Source(**data)
|
|
134
|
+
assert src.transient_mode is True
|
|
135
|
+
|
|
136
|
+
data_off = {**SOURCE_DATA, "transientMode": 0}
|
|
137
|
+
src_off = Source(**data_off)
|
|
138
|
+
assert src_off.transient_mode is False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_source_transient_mode_serializes_as_camel():
|
|
142
|
+
src = Source(**{**SOURCE_DATA, "transientMode": True})
|
|
143
|
+
dumped = src.model_dump(by_alias=True)
|
|
144
|
+
assert "transientMode" in dumped
|
|
145
|
+
assert dumped["transientMode"] is True
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_create_source_with_transient_mode(mock_api, client):
|
|
149
|
+
resp_data = {**SOURCE_DATA, "transientMode": True, "signingSecret": "whsec_abc"}
|
|
150
|
+
mock_api.post("/api/sources").respond(200, json={"source": resp_data})
|
|
151
|
+
src = client.sources.create({
|
|
152
|
+
"name": "HIPAA Source",
|
|
153
|
+
"slug": "hipaa",
|
|
154
|
+
"transient_mode": True,
|
|
155
|
+
})
|
|
156
|
+
assert isinstance(src, SourceWithSecret)
|
|
157
|
+
assert src.transient_mode is True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_create_source_params_serialization():
|
|
161
|
+
params = CreateSourceParams(name="test", transient_mode=True)
|
|
162
|
+
dumped = params.model_dump(by_alias=True, exclude_none=True)
|
|
163
|
+
assert dumped["transientMode"] is True
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_update_source_with_transient_mode(mock_api, client):
|
|
167
|
+
mock_api.patch("/api/sources/src_1").respond(204)
|
|
168
|
+
client.sources.update("src_1", {"transient_mode": True})
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_update_source_params_serialization():
|
|
172
|
+
params = UpdateSourceParams(transient_mode=True)
|
|
173
|
+
dumped = params.model_dump(by_alias=True, exclude_none=True)
|
|
174
|
+
assert dumped["transientMode"] is True
|
|
175
|
+
assert "name" not in dumped
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_get_source_with_transient_mode(mock_api, client):
|
|
179
|
+
data = {**SOURCE_DATA, "transientMode": True}
|
|
180
|
+
mock_api.get("/api/sources/src_1").respond(200, json={"source": data})
|
|
181
|
+
src = client.sources.get("src_1")
|
|
182
|
+
assert src.transient_mode is True
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_list_sources_with_transient_mode(mock_api, client):
|
|
186
|
+
data = {**SOURCE_DATA, "transientMode": True}
|
|
187
|
+
mock_api.get("/api/sources").respond(200, json=make_paginated_response(
|
|
188
|
+
[data], data_key="sources", total=1,
|
|
189
|
+
))
|
|
190
|
+
page = client.sources.list()
|
|
191
|
+
assert page.data[0].transient_mode is True
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.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
|