From f90ff328f6c38560feb3216f9fddfc004b626d50 Mon Sep 17 00:00:00 2001 From: Apify Service Account <64261774+apify-service-account@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:15:42 +0200 Subject: [PATCH 1/3] [TODO]: update generated models from apify-docs PR #2640 --- src/apify_client/_models.py | 12 ++++++++++++ src/apify_client/_typeddicts.py | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/apify_client/_models.py b/src/apify_client/_models.py index 19f5cf6d..57b1db8e 100644 --- a/src/apify_client/_models.py +++ b/src/apify_client/_models.py @@ -3766,6 +3766,18 @@ class WebhookRepresentation(BaseModel): """ Optional template for the HTTP headers sent by the webhook. """ + idempotency_key: Annotated[str | None, Field(alias='idempotencyKey', examples=['fdSJmdP3nfs7sfk3y'])] = None + """ + Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + """ + ignore_ssl_errors: Annotated[bool | None, Field(alias='ignoreSslErrors', examples=[False])] = None + """ + Optional flag to ignore SSL errors when the webhook sends the request. + """ + do_not_retry: Annotated[bool | None, Field(alias='doNotRetry', examples=[False])] = None + """ + Optional flag to skip retrying the webhook request on failure. + """ @docs_group('Models') diff --git a/src/apify_client/_typeddicts.py b/src/apify_client/_typeddicts.py index fa430ab7..e3e3bbb1 100644 --- a/src/apify_client/_typeddicts.py +++ b/src/apify_client/_typeddicts.py @@ -336,6 +336,18 @@ class WebhookRepresentationDict(TypedDict): """ Optional template for the HTTP headers sent by the webhook. """ + idempotency_key: NotRequired[str | None] + """ + Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + """ + ignore_ssl_errors: NotRequired[bool | None] + """ + Optional flag to ignore SSL errors when the webhook sends the request. + """ + do_not_retry: NotRequired[bool | None] + """ + Optional flag to skip retrying the webhook request on failure. + """ @docs_group('Typed dicts') @@ -374,3 +386,15 @@ class WebhookRepresentationCamelDict(TypedDict): """ Optional template for the HTTP headers sent by the webhook. """ + idempotencyKey: NotRequired[str | None] + """ + Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + """ + ignoreSslErrors: NotRequired[bool | None] + """ + Optional flag to ignore SSL errors when the webhook sends the request. + """ + doNotRetry: NotRequired[bool | None] + """ + Optional flag to skip retrying the webhook request on failure. + """ From 20e0b973984df1d96f1530ac67611394adb0a9df Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Tue, 16 Jun 2026 08:35:13 +0200 Subject: [PATCH 2/3] fix: forward idempotency_key, ignore_ssl_errors and do_not_retry for ad-hoc webhooks --- src/apify_client/_utils.py | 26 +++++++--------- tests/unit/test_utils.py | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/apify_client/_utils.py b/src/apify_client/_utils.py index e488b5be..92ef5c63 100644 --- a/src/apify_client/_utils.py +++ b/src/apify_client/_utils.py @@ -288,31 +288,25 @@ def encode_webhooks_to_base64(webhooks: WebhooksList | None) -> str | None: Returns `None` for `None` or an empty list, so the query parameter is omitted. - See `WebhooksList` for the accepted shapes. `WebhookRepresentation` instances are used as-is; `WebhookCreate` - instances are projected onto the `WebhookRepresentation` fields, dropping persistent-only fields like `condition`. - Dict shapes are validated into `WebhookRepresentation` and only fields it declares are kept. + See `WebhooksList` for the accepted shapes. `WebhookRepresentation` instances are used as-is. `WebhookCreate` + instances and dict shapes are projected onto the fields `WebhookRepresentation` declares, dropping anything else + (e.g. persistent-only fields like `condition`). Filtering by the declared field names and aliases means new + ad-hoc fields added to `WebhookRepresentation` flow through automatically, without touching this function. """ if not webhooks: return None representations = list[WebhookRepresentation]() + allowed = _webhook_representation_keys() for webhook in webhooks: if isinstance(webhook, WebhookRepresentation): representations.append(webhook) - elif isinstance(webhook, WebhookCreate): - representations.append( - WebhookRepresentation( - event_types=webhook.event_types, - request_url=webhook.request_url, - payload_template=webhook.payload_template, - headers_template=webhook.headers_template, - ) - ) - else: - allowed = _webhook_representation_keys() - filtered = {k: v for k, v in webhook.items() if k in allowed} - representations.append(WebhookRepresentation.model_validate(filtered)) + continue + + data = webhook.model_dump(by_alias=True) if isinstance(webhook, WebhookCreate) else dict(webhook) + filtered = {key: value for key, value in data.items() if key in allowed} + representations.append(WebhookRepresentation.model_validate(filtered)) data = [r.model_dump(by_alias=True, exclude_none=True) for r in representations] json_string = json.dumps(data).encode(encoding='utf-8') diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 7ecf037c..90e918af 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,6 +1,8 @@ from __future__ import annotations import io +import json +from base64 import b64decode from datetime import timedelta from http import HTTPStatus from typing import TYPE_CHECKING @@ -27,6 +29,7 @@ if TYPE_CHECKING: from apify_client._typeddicts import WebhookRepresentationDict + from apify_client.types import WebhooksList def test_to_safe_id() -> None: @@ -58,6 +61,64 @@ def test_encode_webhooks_to_base64() -> None: ) +@pytest.mark.parametrize( + 'webhooks', + [ + pytest.param( + [ + WebhookCreate( + event_types=['ACTOR.RUN.SUCCEEDED'], + condition=WebhookCondition(), + request_url='https://example.com/run-succeeded', + idempotency_key='some-key', + ignore_ssl_errors=True, + do_not_retry=True, + ), + ], + id='webhook-create-model', + ), + pytest.param( + [ + { + 'event_types': ['ACTOR.RUN.SUCCEEDED'], + 'request_url': 'https://example.com/run-succeeded', + 'idempotency_key': 'some-key', + 'ignore_ssl_errors': True, + 'do_not_retry': True, + }, + ], + id='snake-case-dict', + ), + pytest.param( + [ + { + 'eventTypes': ['ACTOR.RUN.SUCCEEDED'], + 'requestUrl': 'https://example.com/run-succeeded', + 'idempotencyKey': 'some-key', + 'ignoreSslErrors': True, + 'doNotRetry': True, + }, + ], + id='camel-case-dict', + ), + ], +) +def test_encode_webhooks_to_base64_keeps_adhoc_fields(webhooks: WebhooksList) -> None: + """Test that the idempotency key and the SSL/retry flags survive the projection for every accepted shape.""" + result = encode_webhooks_to_base64(webhooks) + + assert result is not None + assert json.loads(b64decode(result)) == [ + { + 'eventTypes': ['ACTOR.RUN.SUCCEEDED'], + 'requestUrl': 'https://example.com/run-succeeded', + 'idempotencyKey': 'some-key', + 'ignoreSslErrors': True, + 'doNotRetry': True, + } + ] + + def test_encode_webhooks_to_base64_from_dicts() -> None: """Test that encode_webhooks_to_base64 accepts plain dicts typed as WebhookRepresentationDict.""" webhooks: list[WebhookRepresentationDict] = [ From dbc4560bd6cf0f70197d4ccceb3af84de811606b Mon Sep 17 00:00:00 2001 From: Apify Service Account <64261774+apify-service-account@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:41:03 +0200 Subject: [PATCH 3/3] [TODO]: update generated models from apify-docs PR #2640 --- src/apify_client/_models.py | 10 +++++++--- src/apify_client/_typeddicts.py | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/apify_client/_models.py b/src/apify_client/_models.py index 57b1db8e..4ee81741 100644 --- a/src/apify_client/_models.py +++ b/src/apify_client/_models.py @@ -3766,17 +3766,21 @@ class WebhookRepresentation(BaseModel): """ Optional template for the HTTP headers sent by the webhook. """ + should_interpolate_strings: Annotated[bool | None, Field(alias='shouldInterpolateStrings', examples=[False])] = None + """ + Flag to also interpolate `{{...}}` variables inside string values of the payload and headers templates. + """ idempotency_key: Annotated[str | None, Field(alias='idempotencyKey', examples=['fdSJmdP3nfs7sfk3y'])] = None """ - Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + Key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. """ ignore_ssl_errors: Annotated[bool | None, Field(alias='ignoreSslErrors', examples=[False])] = None """ - Optional flag to ignore SSL errors when the webhook sends the request. + Flag to ignore SSL errors when the webhook sends the request. """ do_not_retry: Annotated[bool | None, Field(alias='doNotRetry', examples=[False])] = None """ - Optional flag to skip retrying the webhook request on failure. + Flag to skip retrying the webhook request on failure. """ diff --git a/src/apify_client/_typeddicts.py b/src/apify_client/_typeddicts.py index e3e3bbb1..46854805 100644 --- a/src/apify_client/_typeddicts.py +++ b/src/apify_client/_typeddicts.py @@ -336,17 +336,21 @@ class WebhookRepresentationDict(TypedDict): """ Optional template for the HTTP headers sent by the webhook. """ + should_interpolate_strings: NotRequired[bool | None] + """ + Flag to also interpolate `{{...}}` variables inside string values of the payload and headers templates. + """ idempotency_key: NotRequired[str | None] """ - Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + Key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. """ ignore_ssl_errors: NotRequired[bool | None] """ - Optional flag to ignore SSL errors when the webhook sends the request. + Flag to ignore SSL errors when the webhook sends the request. """ do_not_retry: NotRequired[bool | None] """ - Optional flag to skip retrying the webhook request on failure. + Flag to skip retrying the webhook request on failure. """ @@ -386,15 +390,19 @@ class WebhookRepresentationCamelDict(TypedDict): """ Optional template for the HTTP headers sent by the webhook. """ + shouldInterpolateStrings: NotRequired[bool | None] + """ + Flag to also interpolate `{{...}}` variables inside string values of the payload and headers templates. + """ idempotencyKey: NotRequired[str | None] """ - Optional key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. + Key that prevents creating duplicate webhooks, e.g. when the run-starting request is retried. """ ignoreSslErrors: NotRequired[bool | None] """ - Optional flag to ignore SSL errors when the webhook sends the request. + Flag to ignore SSL errors when the webhook sends the request. """ doNotRetry: NotRequired[bool | None] """ - Optional flag to skip retrying the webhook request on failure. + Flag to skip retrying the webhook request on failure. """