Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
66 changes: 66 additions & 0 deletions i18n/de/docusaurus-plugin-content-docs/current/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion i18n/de/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
63 changes: 63 additions & 0 deletions i18n/es/docusaurus-plugin-content-docs/current/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion i18n/es/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
64 changes: 64 additions & 0 deletions i18n/fr/docusaurus-plugin-content-docs/current/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion i18n/fr/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Loading