Skip to content

Commit 074ad0e

Browse files
committed
address feedback, better webhooks
1 parent fce6d07 commit 074ad0e

6 files changed

Lines changed: 65 additions & 27 deletions

File tree

src/apify_client/_models.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from apify_client._models_generated import ActorJobStatus, WebhookCreate
1616

1717
if TYPE_CHECKING:
18-
from apify_client._typeddicts_generated import WebhookCreateDict
18+
from apify_client._types import WebhooksList
1919

2020

2121
@docs_group('Models')
@@ -71,20 +71,25 @@ class WebhookRepresentationList(RootModel[list[WebhookRepresentation]]):
7171
"""List of webhook representations with base64 encoding support."""
7272

7373
@classmethod
74-
def from_webhooks(cls, webhooks: list[WebhookCreate] | list[WebhookCreateDict]) -> WebhookRepresentationList:
75-
"""Construct from a list of `WebhookCreate` models or `WebhookCreateDict` TypedDicts.
74+
def from_webhooks(cls, webhooks: WebhooksList) -> WebhookRepresentationList:
75+
"""Construct from a list of webhooks.
7676
77-
Dicts are validated directly as `WebhookRepresentation`, so only the minimal ad-hoc webhook fields
78-
(`event_types`, `request_url`, and optionally `payload_template`/`headers_template`) are required.
77+
See `WebhooksList` for the accepted shapes. `WebhookRepresentation` instances are used as-is; all
78+
other shapes are validated into `WebhookRepresentation`, keeping only its fields and ignoring any
79+
extras (e.g. `condition`).
7980
"""
8081
representations = list[WebhookRepresentation]()
8182

8283
for webhook in webhooks:
83-
if isinstance(webhook, WebhookCreate):
84+
if isinstance(webhook, WebhookRepresentation):
85+
representations.append(webhook)
86+
elif isinstance(webhook, WebhookCreate):
8487
webhook_dict = webhook.model_dump(mode='json', exclude_none=True)
85-
representations.append(WebhookRepresentation.model_validate(webhook_dict))
88+
webhook_representation = WebhookRepresentation.model_validate(webhook_dict)
89+
representations.append(webhook_representation)
8690
else:
87-
representations.append(WebhookRepresentation.model_validate(webhook))
91+
webhook_representation = WebhookRepresentation.model_validate(webhook)
92+
representations.append(webhook_representation)
8893

8994
return cls(representations)
9095

src/apify_client/_resource_clients/actor.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
RunOrigin,
2525
RunResponse,
2626
UpdateActorRequest,
27-
WebhookCreate,
2827
)
2928
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
3029
from apify_client._utils import encode_key_value_store_record_value, response_to_dict, to_seconds
@@ -51,8 +50,7 @@
5150
WebhookCollectionClient,
5251
WebhookCollectionClientAsync,
5352
)
54-
from apify_client._typeddicts_generated import WebhookCreateDict
55-
from apify_client._types import Timeout
53+
from apify_client._types import Timeout, WebhooksList
5654

5755
_PricingInfo = (
5856
PayPerEventActorPricingInfo
@@ -231,7 +229,7 @@ def start(
231229
run_timeout: timedelta | None = None,
232230
force_permission_level: ActorPermissionLevel | None = None,
233231
wait_for_finish: int | None = None,
234-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
232+
webhooks: WebhooksList | None = None,
235233
timeout: Timeout = 'medium',
236234
) -> Run:
237235
"""Start the Actor and immediately return the Run object.
@@ -305,7 +303,7 @@ def call(
305303
restart_on_error: bool | None = None,
306304
memory_mbytes: int | None = None,
307305
run_timeout: timedelta | None = None,
308-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
306+
webhooks: WebhooksList | None = None,
309307
force_permission_level: ActorPermissionLevel | None = None,
310308
wait_duration: timedelta | None = None,
311309
logger: Logger | None | Literal['default'] = 'default',
@@ -727,7 +725,7 @@ async def start(
727725
run_timeout: timedelta | None = None,
728726
force_permission_level: ActorPermissionLevel | None = None,
729727
wait_for_finish: int | None = None,
730-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
728+
webhooks: WebhooksList | None = None,
731729
timeout: Timeout = 'medium',
732730
) -> Run:
733731
"""Start the Actor and immediately return the Run object.
@@ -801,7 +799,7 @@ async def call(
801799
restart_on_error: bool | None = None,
802800
memory_mbytes: int | None = None,
803801
run_timeout: timedelta | None = None,
804-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
802+
webhooks: WebhooksList | None = None,
805803
force_permission_level: ActorPermissionLevel | None = None,
806804
wait_duration: timedelta | None = None,
807805
logger: Logger | None | Literal['default'] = 'default',

src/apify_client/_resource_clients/task.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
TaskOptions,
1515
TaskResponse,
1616
UpdateTaskRequest,
17-
WebhookCreate,
1817
)
1918
from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync
2019
from apify_client._utils import response_to_dict, to_seconds
@@ -31,8 +30,8 @@
3130
WebhookCollectionClient,
3231
WebhookCollectionClientAsync,
3332
)
34-
from apify_client._typeddicts_generated import TaskInputDict, WebhookCreateDict
35-
from apify_client._types import Timeout
33+
from apify_client._typeddicts_generated import TaskInputDict
34+
from apify_client._types import Timeout, WebhooksList
3635

3736

3837
@docs_group('Resource clients')
@@ -162,7 +161,7 @@ def start(
162161
run_timeout: timedelta | None = None,
163162
restart_on_error: bool | None = None,
164163
wait_for_finish: int | None = None,
165-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
164+
webhooks: WebhooksList | None = None,
166165
timeout: Timeout = 'medium',
167166
) -> Run:
168167
"""Start the task and immediately return the Run object.
@@ -229,7 +228,7 @@ def call(
229228
memory_mbytes: int | None = None,
230229
run_timeout: timedelta | None = None,
231230
restart_on_error: bool | None = None,
232-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
231+
webhooks: WebhooksList | None = None,
233232
wait_duration: timedelta | None = None,
234233
timeout: Timeout = 'no_timeout',
235234
) -> Run | None:
@@ -485,7 +484,7 @@ async def start(
485484
run_timeout: timedelta | None = None,
486485
restart_on_error: bool | None = None,
487486
wait_for_finish: int | None = None,
488-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
487+
webhooks: WebhooksList | None = None,
489488
timeout: Timeout = 'medium',
490489
) -> Run:
491490
"""Start the task and immediately return the Run object.
@@ -552,7 +551,7 @@ async def call(
552551
memory_mbytes: int | None = None,
553552
run_timeout: timedelta | None = None,
554553
restart_on_error: bool | None = None,
555-
webhooks: list[WebhookCreate] | list[WebhookCreateDict] | None = None,
554+
webhooks: WebhooksList | None = None,
556555
wait_duration: timedelta | None = None,
557556
timeout: Timeout = 'no_timeout',
558557
) -> Run | None:

src/apify_client/_typeddicts.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,25 @@ class RequestDeleteInputDict(TypedDict):
4040

4141
unique_key: NotRequired[str | None]
4242
"""A unique key used for request de-duplication."""
43+
44+
45+
@docs_group('Typed dicts')
46+
class WebhookRepresentationDict(TypedDict):
47+
"""TypedDict counterpart of `WebhookRepresentation` for ad-hoc webhooks.
48+
49+
Captures the minimal shape the Apify API actually requires when attaching ad-hoc webhooks to a run:
50+
only `event_types` and `request_url` are mandatory. Unlike `WebhookCreateDict`, there is no `condition`
51+
field — the API does not use it for ad-hoc webhooks.
52+
"""
53+
54+
event_types: list[str]
55+
"""The list of Actor events that trigger this webhook."""
56+
57+
request_url: str
58+
"""The URL to which the webhook sends its payload."""
59+
60+
payload_template: NotRequired[str | None]
61+
"""Optional template for the JSON payload sent by the webhook."""
62+
63+
headers_template: NotRequired[str | None]
64+
"""Optional template for the HTTP headers sent by the webhook."""

src/apify_client/_types.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33
from datetime import timedelta
44
from typing import Literal
55

6+
from apify_client._models import WebhookRepresentation
7+
from apify_client._models_generated import WebhookCreate
8+
from apify_client._typeddicts import WebhookRepresentationDict
9+
from apify_client._typeddicts_generated import WebhookCreateDict
10+
11+
WebhooksList = (
12+
list[WebhookCreate] | list[WebhookCreateDict] | list[WebhookRepresentation] | list[WebhookRepresentationDict]
13+
)
14+
"""Type for the `webhooks` parameter on resource-client `start`/`call` methods and `from_webhooks`.
15+
16+
`WebhookRepresentation` / `WebhookRepresentationDict` are the minimal ad-hoc webhook shape (only
17+
`event_types` and `request_url` required). `WebhookCreate` / `WebhookCreateDict` are accepted so a
18+
persistent-webhook definition can be reused; their fields not relevant to ad-hoc webhooks (e.g.
19+
`condition`) are ignored at runtime.
20+
"""
21+
622
Timeout = timedelta | Literal['no_timeout', 'short', 'medium', 'long']
723
"""Type for the `timeout` parameter on resource client methods.
824

tests/unit/test_utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from apify_client.errors import ApifyApiError, InvalidResponseBodyError
2727

2828
if TYPE_CHECKING:
29-
from apify_client._typeddicts_generated import WebhookCreateDict
29+
from apify_client._typeddicts import WebhookRepresentationDict
3030

3131

3232
def test_to_safe_id() -> None:
@@ -59,16 +59,14 @@ def test_webhook_representation_list_to_base64() -> None:
5959

6060

6161
def test_webhook_representation_list_from_dicts() -> None:
62-
"""Test that from_webhooks accepts plain dicts typed as WebhookCreateDict."""
63-
webhooks: list[WebhookCreateDict] = [
62+
"""Test that from_webhooks accepts plain dicts typed as WebhookRepresentationDict."""
63+
webhooks: list[WebhookRepresentationDict] = [
6464
{
6565
'event_types': ['ACTOR.RUN.CREATED'],
66-
'condition': {},
6766
'request_url': 'https://example.com/run-created',
6867
},
6968
{
7069
'event_types': ['ACTOR.RUN.SUCCEEDED'],
71-
'condition': {},
7270
'request_url': 'https://example.com/run-succeeded',
7371
'payload_template': '{"hello": "world"}',
7472
},

0 commit comments

Comments
 (0)