diff --git a/docs/polyglot/python.md b/docs/polyglot/python.md index dd3a1bfe2b5..785904c85f0 100644 --- a/docs/polyglot/python.md +++ b/docs/polyglot/python.md @@ -1079,6 +1079,69 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Received retry policy and original attempt {#activity-retry-metadata} + +Requires Python SDK **2.12.0 or later**. `info = activity.context().info` +exposes the original `attempt_number` and an immutable `retry_info` snapshot. +`info.retry_info.policy()` returns a fresh copy, including unknown fields. + +A missing policy and root `retry_policy: null` both have an unknown retry +limit. `policy_provided` distinguishes them (`False` and `True`). +A received policy object with `max_attempts: null` explicitly means unlimited: +`is_unlimited()` is `True`, while `max_attempts()` remains `None`. +For a positive integer limit, `max_attempts()` returns that limit and +`is_last_configured_attempt()` compares it with the original attempt. +An unknown limit returns `None` for that last-attempt check. + +This check describes the received budget. Cancellation, deadlines, workflow +closure and non-retryable failures can end execution earlier. Ordinary and +supervised remote callbacks receive the original snapshot. Inline and prepared +local callbacks do not inherit the parent's remote retry policy. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Signal the originating run {#signal-originating-run} + +`info.workflow_id` and `info.run_id` preserve the original received identities +and can be `None`. Local callbacks inherit only known parent identities. +The SDK never substitutes a task ID. Pass an authorized application client +to this helper; the handle pins the original run: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Keep `business_event_id` stable across retries and within the Server's +command-key length limit. The currently published Server does not retain +these keys for every signal path. Make business-event processing idempotent +and reconcile uncertain delivery. See the +[canonical activity reference](https://python.durable-workflow.com/reference/activity/) +and [signal receipt-retention requirement](https://github.com/durable-workflow/.github/issues/216). + ### Heartbeat requests {#activity-heartbeat-requests} For remote activities, call `await ctx.heartbeat(...)` during bounded work, well before `heartbeat_timeout`. Its first window starts at runtime claim, before Python receives the task. Each recorded heartbeat uses the runtime's recording time, not the time its response reaches Python. Pauses, request latency and transport retries consume the budget. Follow the [cadence guidance](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md index 021624cf81c..5140de8ffbd 100644 --- a/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1052,6 +1052,72 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Empfangene Wiederholungsrichtlinie und ursprünglicher Versuch {#activity-retry-metadata} + +Erfordert Python SDK **2.12.0 oder neuer**. `info = activity.context().info` +liefert die ursprüngliche `attempt_number` und einen unveränderlichen +`retry_info`-Snapshot. `info.retry_info.policy()` gibt eine neue Kopie +einschließlich unbekannter Felder zurück. + +Eine fehlende Richtlinie und `retry_policy: null` auf der obersten Ebene +haben beide eine unbekannte Versuchsgrenze. `policy_provided` unterscheidet +sie (`False` und `True`). Ein empfangenes Richtlinienobjekt mit +`max_attempts: null` bedeutet ausdrücklich unbegrenzte Versuche: +`is_unlimited()` ist `True`, während `max_attempts()` `None` bleibt. +Bei einer positiven ganzzahligen Grenze gibt `max_attempts()` diese Grenze +zurück. `is_last_configured_attempt()` vergleicht sie mit dem ursprünglichen +Versuch. Bei unbekannter Grenze liefert diese Prüfung `None`. + +Die Prüfung beschreibt das empfangene Budget. Abbruch, Fristen, das Schließen +des Workflows und nicht wiederholbare Fehler können die Ausführung früher beenden. +Normale und überwachte Remote-Callbacks erhalten den ursprünglichen Snapshot. +Lokale Inline- und vorbereitete Callbacks erben die Remote-Richtlinie des Eltern-Workflows nicht. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Ein Signal an den ursprünglichen Run senden {#signal-originating-run} + +`info.workflow_id` und `info.run_id` bewahren die ursprünglich empfangenen +Kennungen und können `None` sein. Lokale Callbacks erben nur bekannte +übergeordnete Kennungen. Das SDK ersetzt sie niemals durch eine Task-ID. +Übergeben Sie diesem Helfer einen autorisierten Anwendungsclient. +Das Handle bleibt an den ursprünglichen Run gebunden: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Halten Sie `business_event_id` über Wiederholungen hinweg stabil und beachten +Sie die Längengrenze für Command Keys des Servers. Der aktuell veröffentlichte +Server bewahrt diese Schlüssel nicht für jeden Signalpfad auf. +Verarbeiten Sie Geschäftsereignisse idempotent und gleichen Sie unklare +Zustellungen ab. Siehe die [kanonische Aktivitätsreferenz](https://python.durable-workflow.com/reference/activity/) +und die [Anforderung zur Aufbewahrung von Signalbelegen](https://github.com/durable-workflow/.github/issues/216). + ### Heartbeat-Anfragen {#activity-heartbeat-requests} Rufen Sie bei entfernten Aktivitäten `await ctx.heartbeat(...)` während begrenzter Aktivitätsarbeit deutlich vor `heartbeat_timeout` auf. Das erste Zeitfenster beginnt mit der Beanspruchung durch die Laufzeit, bevor Python die Aufgabe empfängt. Jeder aufgezeichnete Heartbeat verwendet den Aufzeichnungszeitpunkt der Laufzeit, nicht den Empfangszeitpunkt der Antwort in Python. Pausen, Anfragelatenz und Transportwiederholungen verbrauchen das Budget. Beachten Sie die [Taktempfehlungen](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/de/source-hashes.json b/i18n/de/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/de/source-hashes.json +++ b/i18n/de/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md index 1d5bd1af954..84d925896c3 100644 --- a/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1029,6 +1029,69 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Política de reintentos recibida e intento original {#activity-retry-metadata} + +Requiere Python SDK **2.12.0 o posterior**. `info = activity.context().info` +expone el `attempt_number` original y una instantánea inmutable `retry_info`. +`info.retry_info.policy()` devuelve una copia nueva, incluidos los campos desconocidos. + +Una política ausente y `retry_policy: null` en la raíz tienen un límite +desconocido. `policy_provided` las distingue (`False` y `True`). +Un objeto de política recibido con `max_attempts: null` indica explícitamente +intentos ilimitados: `is_unlimited()` devuelve `True` y `max_attempts()` +sigue siendo `None`. Con un límite entero positivo, `max_attempts()` devuelve +el límite e `is_last_configured_attempt()` lo compara con el intento original. +Si el límite es desconocido, la comprobación del último intento devuelve `None`. + +La comprobación describe el presupuesto recibido. La cancelación, los plazos, +el cierre del workflow y los errores no reintentables pueden terminar antes. +Los callbacks remotos ordinarios y supervisados reciben la instantánea original. +Los callbacks locales inline y preparados no heredan la política remota del padre. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Enviar una señal a la ejecución original {#signal-originating-run} + +`info.workflow_id` e `info.run_id` conservan las identidades originales recibidas +y pueden ser `None`. Los callbacks locales solo heredan identidades conocidas +del padre. El SDK nunca las sustituye por un ID de tarea. Pase un cliente de la +aplicación autorizado a este helper; el handle queda vinculado al run original: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Mantenga `business_event_id` estable entre reintentos y dentro del límite de +longitud de claves de comando del Server. El Server publicado actualmente no +conserva estas claves en todas las rutas de señales. Procese los eventos de +negocio de forma idempotente y reconcilie las entregas inciertas. Consulte la +[referencia canónica de actividades](https://python.durable-workflow.com/reference/activity/) +y el [requisito de conservación de recibos de señales](https://github.com/durable-workflow/.github/issues/216). + ### Peticiones de heartbeat {#activity-heartbeat-requests} Para actividades remotas, llame a `await ctx.heartbeat(...)` durante trabajo de actividad acotado, con suficiente antelación a `heartbeat_timeout`. La primera ventana empieza al reclamar la actividad en el entorno de ejecución, antes de que Python reciba la tarea. Cada heartbeat registrado usa el momento en que lo registra el entorno, no cuando su respuesta llega a Python. Las pausas, la latencia y los reintentos de transporte consumen el presupuesto. Siga las [recomendaciones de cadencia](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/es/source-hashes.json b/i18n/es/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/es/source-hashes.json +++ b/i18n/es/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md index 7f74cbcbec8..30bc98e5c70 100644 --- a/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1052,6 +1052,70 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Politique de reprise reçue et tentative initiale {#activity-retry-metadata} + +Nécessite Python SDK **2.12.0 ou ultérieur**. `info = activity.context().info` +expose le `attempt_number` initial et un instantané immuable `retry_info`. +`info.retry_info.policy()` renvoie une nouvelle copie, y compris les champs inconnus. + +Une politique absente et `retry_policy: null` à la racine ont une limite inconnue. +`policy_provided` distingue les deux cas (`False` et `True`). +Un objet de politique reçu avec `max_attempts: null` signifie explicitement +des tentatives illimitées : `is_unlimited()` renvoie `True`, tandis que +`max_attempts()` reste `None`. Pour une limite entière positive, +`max_attempts()` renvoie cette limite et `is_last_configured_attempt()` la +compare à la tentative initiale. Si la limite est inconnue, ce dernier test +renvoie `None`. + +Ce test décrit le budget reçu. Une annulation, une échéance, la fermeture du +workflow ou un échec sans reprise peuvent arrêter l'exécution plus tôt. +Les callbacks distants ordinaires et supervisés reçoivent l'instantané initial. +Les callbacks locaux inline et préparés n'héritent pas de la politique distante du parent. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Envoyer un signal à l'exécution initiale {#signal-originating-run} + +`info.workflow_id` et `info.run_id` conservent les identités initiales reçues +et peuvent valoir `None`. Les callbacks locaux héritent uniquement des identités +parentes connues. Le SDK ne les remplace jamais par un ID de tâche. +Passez un client applicatif autorisé à cet utilitaire. Le handle reste lié au run initial : + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Gardez `business_event_id` stable entre les tentatives et respectez la limite +de longueur des clés de commande du Server. Le Server actuellement publié ne +conserve pas ces clés pour tous les chemins de signal. Traitez les événements +métier de façon idempotente et réconciliez les livraisons incertaines. Consultez +[la référence canonique des activités](https://python.durable-workflow.com/reference/activity/) +et [l'exigence de conservation des reçus de signal](https://github.com/durable-workflow/.github/issues/216). + ### Requêtes de heartbeat {#activity-heartbeat-requests} Pour les activités distantes, appelez `await ctx.heartbeat(...)` pendant un travail d’activité borné, bien avant `heartbeat_timeout`. La première fenêtre commence lorsque le moteur réclame l’activité, avant que Python reçoive la tâche. Chaque heartbeat enregistré utilise le moment de son enregistrement par le moteur, pas celui où sa réponse atteint Python. Les pauses, la latence et les nouvelles tentatives de transport consomment le budget. Suivez les [conseils de fréquence](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/fr/source-hashes.json b/i18n/fr/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/fr/source-hashes.json +++ b/i18n/fr/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md index f81fd5bb773..0b0826b58f8 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -873,6 +873,68 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### 受信した再試行ポリシーと元の試行回数 {#activity-retry-metadata} + +Python SDK **2.12.0 以降**が必要です。`info = activity.context().info` は +元の `attempt_number` と不変の `retry_info` スナップショットを提供します。 +`info.retry_info.policy()` は未知のフィールドも含む新しいコピーを返します。 + +ポリシーがない場合も、ルートの `retry_policy: null` の場合も、試行上限は不明です。 +`policy_provided` は両者を `False` と `True` で区別します。 +受信したポリシーオブジェクトの `max_attempts: null` は、明示的に無制限を意味します。 +この場合、`is_unlimited()` は `True`、`max_attempts()` は `None` です。 +正の整数の上限があれば、`max_attempts()` はその値を返し、 +`is_last_configured_attempt()` は元の試行回数と比較します。 +上限が不明な場合、最後の試行かどうかの判定は `None` を返します。 + +この判定は受信した再試行予算を表します。キャンセル、期限、ワークフローの終了、 +再試行できない失敗により、上限より前に実行が終わる場合があります。 +通常および監視付きのリモートコールバックは元のスナップショットを受け取ります。 +インラインおよび準備済みローカルコールバックは、親のリモート再試行ポリシーを継承しません。 + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### 元の実行にシグナルを送る {#signal-originating-run} + +`info.workflow_id` と `info.run_id` は受信した元の識別子を保持し、 +`None` の場合もあります。ローカルコールバックは既知の親識別子だけを継承します。 +SDK がタスク ID で代用することはありません。認可済みのアプリケーションクライアントを +このヘルパーに渡してください。ハンドルは元の実行に固定されます。 + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +`business_event_id` は再試行の間で一定に保ち、Server のコマンドキーの長さ制限に +収めてください。現在公開されている Server は、すべてのシグナル経路でこれらのキーを +保持するわけではありません。ビジネスイベントの処理を冪等にし、配信結果が不確かな場合は +照合してください。[アクティビティの正式なリファレンス](https://python.durable-workflow.com/reference/activity/) +と[シグナル受領記録の保持要件](https://github.com/durable-workflow/.github/issues/216)を参照してください。 + ### ハートビートのリクエスト {#activity-heartbeat-requests} リモートアクティビティでは、上限を設けた処理の途中で、`heartbeat_timeout` より十分前に `await ctx.heartbeat(...)` を呼び出します。最初の期間はランタイムのタスク取得から始まり、Python の受信より前です。記録されたハートビートはランタイムでの記録時刻を使い、Python が応答を受信した時刻は使いません。中断、リクエストの遅延、通信の再試行も時間予算を消費します。[送信間隔の指針](../features/heartbeats.md#heartbeat-cadence-and-receipts)に従ってください。 diff --git a/i18n/ja/source-hashes.json b/i18n/ja/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/ja/source-hashes.json +++ b/i18n/ja/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md index 39176ba1aff..7c37824c52a 100644 --- a/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/pt-BR/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1052,6 +1052,69 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Política de tentativas recebida e tentativa original {#activity-retry-metadata} + +Requer Python SDK **2.12.0 ou posterior**. `info = activity.context().info` +expõe o `attempt_number` original e um snapshot imutável `retry_info`. +`info.retry_info.policy()` retorna uma nova cópia, incluindo campos desconhecidos. + +Uma política ausente e `retry_policy: null` na raiz têm limite desconhecido. +`policy_provided` distingue os casos (`False` e `True`). +Um objeto de política recebido com `max_attempts: null` indica explicitamente +tentativas ilimitadas: `is_unlimited()` retorna `True`, enquanto +`max_attempts()` permanece `None`. Para um limite inteiro positivo, +`max_attempts()` retorna esse limite e `is_last_configured_attempt()` o compara +com a tentativa original. Um limite desconhecido retorna `None` nessa verificação. + +A verificação descreve o orçamento recebido. Cancelamentos, prazos, encerramento +do workflow e falhas não repetíveis podem encerrar a execução antes desse limite. +Callbacks remotos comuns e supervisionados recebem o snapshot original. +Callbacks locais inline e preparados não herdam a política remota do pai. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Enviar um sinal ao run original {#signal-originating-run} + +`info.workflow_id` e `info.run_id` preservam as identidades originais recebidas +e podem ser `None`. Callbacks locais herdam apenas identidades conhecidas do pai. +O SDK nunca as substitui por um ID de tarefa. Passe um cliente autorizado da +aplicação para este helper; o handle fica vinculado ao run original: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Mantenha `business_event_id` estável entre tentativas e dentro do limite de +comprimento de chaves de comando do Server. O Server publicado atualmente não +retém essas chaves em todos os caminhos de sinal. Processe eventos de negócio +de forma idempotente e reconcilie entregas incertas. Consulte a +[referência canônica de atividades](https://python.durable-workflow.com/reference/activity/) +e o [requisito de retenção de recibos de sinal](https://github.com/durable-workflow/.github/issues/216). + ### Requisições de heartbeat {#activity-heartbeat-requests} Para atividades remotas, chame `await ctx.heartbeat(...)` durante trabalho de atividade limitado, bem antes de `heartbeat_timeout`. A primeira janela começa na reivindicação pelo runtime, antes de o Python receber a tarefa. Cada heartbeat registrado usa o horário em que o runtime o registra, não quando a resposta chega ao Python. Pausas, latência das requisições e novas tentativas de transporte consomem o orçamento. Siga as [orientações de cadência](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/pt-BR/source-hashes.json b/i18n/pt-BR/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/pt-BR/source-hashes.json +++ b/i18n/pt-BR/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md index ac323d1004b..a65528caf70 100644 --- a/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/uk/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -1037,6 +1037,69 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### Отримана retry policy та початкова спроба {#activity-retry-metadata} + +Потрібен Python SDK **2.12.0 або новіший**. `info = activity.context().info` +надає початковий `attempt_number` і незмінний знімок `retry_info`. +`info.retry_info.policy()` повертає нову копію, включно з невідомими полями. + +Відсутня policy та кореневе `retry_policy: null` мають невідомий ліміт +спроб. `policy_provided` розрізняє їх (`False` і `True`). +Отриманий об’єкт policy з `max_attempts: null` явно означає необмежені спроби: +`is_unlimited()` повертає `True`, а `max_attempts()` залишається `None`. +Для додатного цілого ліміту `max_attempts()` повертає цей ліміт, а +`is_last_configured_attempt()` порівнює його з початковим номером спроби. +Для невідомого ліміту перевірка останньої спроби повертає `None`. + +Перевірка описує отриманий бюджет. Скасування, строки, закриття workflow та +помилки без повторних спроб можуть завершити виконання раніше. Звичайні й +supervised віддалені callback отримують початковий знімок. Локальні inline +та prepared callback не успадковують віддалену retry policy батьківського workflow. + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### Надіслати signal до початкового run {#signal-originating-run} + +`info.workflow_id` і `info.run_id` зберігають початкові отримані ідентифікатори +й можуть бути `None`. Локальні callback успадковують лише відомі ідентифікатори +батьківського workflow. SDK ніколи не підставляє task ID. Передайте до helper +авторизований клієнт застосунку. Handle прив’язаний до початкового run: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +Зберігайте `business_event_id` незмінним між спробами та в межах ліміту довжини +command key Server. Поточний опублікований Server зберігає ці ключі не для всіх +шляхів signal. Обробляйте бізнес-події ідемпотентно й узгоджуйте невизначені +результати доставки. Див. +[основний довідник activity](https://python.durable-workflow.com/reference/activity/) +і [вимогу збереження квитанцій signal](https://github.com/durable-workflow/.github/issues/216). + ### Запити heartbeat {#activity-heartbeat-requests} Для віддалених activities викликайте `await ctx.heartbeat(...)` під час обмеженої роботи activity, задовго до `heartbeat_timeout`. Перше вікно починається під час отримання runtime повноважень на завдання, ще до доставлення в Python. Кожен записаний heartbeat використовує час запису в runtime, а не час отримання відповіді Python. Паузи, затримки запитів і транспортні повтори витрачають бюджет. Дотримуйтеся [порад щодо періодичності](../features/heartbeats.md#heartbeat-cadence-and-receipts). diff --git a/i18n/uk/source-hashes.json b/i18n/uk/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/uk/source-hashes.json +++ b/i18n/uk/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1", diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md index e065cee3d02..8e9d27a7725 100644 --- a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/polyglot/python.md @@ -859,6 +859,66 @@ async def long_running_task(items: list) -> dict: return {"processed": len(items)} ``` +#### 收到的重试策略与原始尝试次数 {#activity-retry-metadata} + +需要 Python SDK **2.12.0 或更高版本**。`info = activity.context().info` +提供原始 `attempt_number` 和不可变的 `retry_info` 快照。 +`info.retry_info.policy()` 返回新的副本,并保留未知字段。 + +缺少策略与根级 `retry_policy: null` 都表示重试上限未知; +`policy_provided` 用 `False` 和 `True` 区分这两种情况。 +收到的策略对象中 `max_attempts: null` 明确表示无限重试: +`is_unlimited()` 为 `True`,而 `max_attempts()` 仍为 `None`。 +对于正整数上限,`max_attempts()` 返回该上限, +`is_last_configured_attempt()` 将其与原始尝试次数比较。 +上限未知时,最后一次尝试检查返回 `None`。 + +此检查只描述收到的重试预算。取消、截止时间、工作流关闭或不可重试的失败 +都可能提前结束执行。普通和受监督的远程回调收到相同的原始快照。 +内联和预备本地回调不会继承父工作流的远程重试策略。 + +```python +from durable_workflow import activity + + +@activity.defn(name="inspect_retry_budget") +async def inspect_retry_budget() -> dict: + retry = activity.context().info.retry_info + return { + "attempt": retry.attempt_number, + "max_attempts": retry.max_attempts(), + "last_configured_attempt": retry.is_last_configured_attempt(), + } +``` + +#### 向原始运行发送信号 {#signal-originating-run} + +`info.workflow_id` 和 `info.run_id` 保留收到的原始标识,也可能为 `None`。 +本地回调只继承已知的父级标识。SDK 不会使用任务 ID 代替缺失标识。 +将获得授权的应用客户端传给此辅助函数,句柄会固定到原始运行: + +```python +from durable_workflow import Client, activity + + +async def signal_originating_run(client: Client, business_event_id: str) -> dict: + info = activity.context().info + if info.workflow_id is None or info.run_id is None: + raise RuntimeError("This activity has no known originating workflow run") + handle = client.get_workflow_handle(info.workflow_id, run_id=info.run_id) + return await handle.signal( + "payment_recorded", + [business_event_id], + request_id=f"payment-recorded:{business_event_id}", + ) +``` + +重试时保持 `business_event_id` 不变,并遵守 Server 命令键的长度限制。 +当前已发布的 Server 不会在所有信号路径上保留这些键。 +业务事件处理应具有幂等性,并对结果不确定的投递进行核对。参阅 +[活动的权威参考文档](https://python.durable-workflow.com/reference/activity/) +和[信号回执保留要求](https://github.com/durable-workflow/.github/issues/216)。 + ### 心跳请求 {#activity-heartbeat-requests} 对于远程活动,在有界工作期间调用 `await ctx.heartbeat(...)`,并在 `heartbeat_timeout` 到期前留足时间。第一个窗口从运行时领取任务开始,早于 Python 收到任务。每个已记录心跳使用运行时的记录时间,而不是响应到达 Python 的时间。暂停、请求延迟和传输重试都会消耗预算。请遵循[发送节奏建议](../features/heartbeats.md#heartbeat-cadence-and-receipts)。 diff --git a/i18n/zh-Hans/source-hashes.json b/i18n/zh-Hans/source-hashes.json index f0d440562a0..6a298b19a87 100644 --- a/i18n/zh-Hans/source-hashes.json +++ b/i18n/zh-Hans/source-hashes.json @@ -82,7 +82,7 @@ "polyglot/polling-patterns.md": "30875099a23731c6c471b481a771378acc6ff57eed2fda4405a509057534c989", "polyglot/portable-worker-affinity.md": "40973c6a31a76845b4f95e9a505e68ad76abd5174bbdccf37993cdebc8904959", "polyglot/prerelease-history-migration.md": "a57f05f492228a8a67791f9152648a1e5f80856325b78777cf63b6ef49b7413c", - "polyglot/python.md": "ac9cd626b7ac3d444509d933f2fec3af015f4c4c171abb0bfcf32642282d7536", + "polyglot/python.md": "1908679d0a02c538dbcc6aed41715799b1b7b89d67e9517b4bf3658196174dd5", "polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3", "polyglot/rust.md": "af91b22c93a7fe6833df156371d99bb105740684915bc73af4576f6de2897e10", "polyglot/server-api-reference.md": "b0a7bfa17ccaec0507ee97dfc1badac49fa5ff96f8178fa4ce5963bc8c6fe7d1",