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
36 changes: 36 additions & 0 deletions docs/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,42 @@ async def validate(data: dict) -> dict:
return data
```

### Terminal business errors {#terminal-business-errors}

`non_retryable_error_types` matches the **outermost** exception's short name (`exception_type`) or fully qualified class name (`exception_class`) exactly. For example, `ValidationError` does not match `SpecificValidationError`, even if it is a subclass. A qualified entry such as `app.errors.ValidationError` must match that recorded class name. The policy does not walk subclasses or exception causes. `raise RuntimeError(...) from NonRetryableError(...)` reports the outer `RuntimeError` and can still be retried.

When converting a known business exception, make `NonRetryableError` the outer exception and keep the original Python cause with `cause=error`. This marks the activity failure non-retryable without relying on a name list. It does not make the entire workflow fail automatically: workflow code receives `ActivityFailed` when the activity has no retries left, either because it is non-retryable or because its retry budget is exhausted.

```python
from durable_workflow import ActivityFailed, NonRetryableError, activity, workflow

@activity.defn(name="validate_order")
async def validate_order(order: dict) -> dict:
try:
if order.get("amount", 0) <= 0:
raise ValueError("amount must be positive")
except ValueError as error:
raise NonRetryableError("Order validation failed", cause=error) from error
return order

@workflow.defn(name="place_order")
class PlaceOrderWorkflow:
def run(self, ctx, order: dict):
try:
yield ctx.schedule_activity("validate_order", [order])
except ActivityFailed as failure:
if (
failure.activity_type != "validate_order"
or failure.exception_type != "NonRetryableError"
or not failure.non_retryable
):
raise
return {"order_id": order["id"], "status": "rejected"}
return {"order_id": order["id"], "status": "accepted"}
```

For input `{"id": "order-123", "amount": 0}`, the activity fails once and the workflow completes normally with `status: rejected`. Handle only the business failure you intend to handle; the example rethrows other failures. Continue with further durable steps if your business flow requires them. An uncaught `ActivityFailed` fails the workflow. Preserving a Python cause does not mean retry policy examines that chain or that every SDK exposes an identical cause object.

### Synchronous and asynchronous activities {#activity-execution}

Ordinary `def` remote activities run in a dedicated thread pool bounded by
Expand Down
36 changes: 36 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 @@ -1053,6 +1053,42 @@ async def validate(data: dict) -> dict:
return data
```

### Terminale fachliche Fehler {#terminal-business-errors}

`non_retryable_error_types` vergleicht den kurzen Namen (`exception_type`) oder vollständig qualifizierten Klassennamen (`exception_class`) der **äußersten** Ausnahme exakt. `ValidationError` passt beispielsweise nicht zu `SpecificValidationError`, auch wenn diese Klasse davon erbt. Ein qualifizierter Eintrag wie `app.errors.ValidationError` muss dem aufgezeichneten Klassennamen entsprechen. Die Richtlinie durchsucht weder Unterklassen noch Ursachen. `raise RuntimeError(...) from NonRetryableError(...)` meldet den äußeren `RuntimeError` und kann weiterhin wiederholt werden.

Wenn Sie eine bekannte fachliche Ausnahme umwandeln, lösen Sie `NonRetryableError` als äußere Ausnahme aus und erhalten Sie die ursprüngliche Python-Ursache mit `cause=error`. Dadurch wird der Aktivitätsfehler ohne Namensliste als nicht wiederholbar markiert. Das lässt nicht automatisch den ganzen Workflow scheitern: Er erhält `ActivityFailed`, wenn keine Aktivitätsversuche verbleiben, entweder wegen eines nicht wiederholbaren Fehlers oder wegen ausgeschöpften Budgets.

```python
from durable_workflow import ActivityFailed, NonRetryableError, activity, workflow

@activity.defn(name="validate_order")
async def validate_order(order: dict) -> dict:
try:
if order.get("amount", 0) <= 0:
raise ValueError("amount must be positive")
except ValueError as error:
raise NonRetryableError("Order validation failed", cause=error) from error
return order

@workflow.defn(name="place_order")
class PlaceOrderWorkflow:
def run(self, ctx, order: dict):
try:
yield ctx.schedule_activity("validate_order", [order])
except ActivityFailed as failure:
if (
failure.activity_type != "validate_order"
or failure.exception_type != "NonRetryableError"
or not failure.non_retryable
):
raise
return {"order_id": order["id"], "status": "rejected"}
return {"order_id": order["id"], "status": "accepted"}
```

Bei der Eingabe `{"id": "order-123", "amount": 0}` schlägt die Aktivität einmal fehl und der Workflow schließt normal mit `status: rejected` ab. Behandeln Sie nur den vorgesehenen fachlichen Fehler; das Beispiel löst andere Fehler erneut aus. Fahren Sie mit weiteren dauerhaften Schritten fort, wenn der Geschäftsablauf es erfordert. Ein unbehandelter `ActivityFailed` lässt den Workflow scheitern. Eine erhaltene Python-Ursache bedeutet weder, dass die Wiederholungsrichtlinie die Kette prüft, noch dass jedes SDK dasselbe Ursachenobjekt bereitstellt.

### Synchrone und asynchrone Aktivitäten {#activity-execution}

Gewöhnliche entfernte `def`-Aktivitäten laufen in einem eigenen Threadpool,
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": "a9fc8330cbc78953244b94a170197038a299a5a25fa78c38c7bfb451fd67d345",
"polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1",
"polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3",
"polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0",
"polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83",
Expand Down
36 changes: 36 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 @@ -1030,6 +1030,42 @@ async def validate(data: dict) -> dict:
return data
```

### Errores de negocio terminales {#terminal-business-errors}

`non_retryable_error_types` compara exactamente el nombre corto (`exception_type`) o el nombre de clase cualificado (`exception_class`) de la excepción **exterior**. Por ejemplo, `ValidationError` no coincide con `SpecificValidationError`, aunque sea una subclase. Una entrada cualificada como `app.errors.ValidationError` debe coincidir con la clase registrada. La política no recorre subclases ni causas. `raise RuntimeError(...) from NonRetryableError(...)` comunica el `RuntimeError` exterior y puede seguir reintentándose.

Al convertir una excepción de negocio conocida, lance `NonRetryableError` como excepción exterior y conserve la causa original de Python mediante `cause=error`. Esto marca el fallo de actividad como no reintentable sin depender de una lista de nombres. No hace fallar automáticamente el workflow completo: este recibe `ActivityFailed` cuando no quedan reintentos, por una negativa no reintentable o por agotar el presupuesto de intentos.

```python
from durable_workflow import ActivityFailed, NonRetryableError, activity, workflow

@activity.defn(name="validate_order")
async def validate_order(order: dict) -> dict:
try:
if order.get("amount", 0) <= 0:
raise ValueError("amount must be positive")
except ValueError as error:
raise NonRetryableError("Order validation failed", cause=error) from error
return order

@workflow.defn(name="place_order")
class PlaceOrderWorkflow:
def run(self, ctx, order: dict):
try:
yield ctx.schedule_activity("validate_order", [order])
except ActivityFailed as failure:
if (
failure.activity_type != "validate_order"
or failure.exception_type != "NonRetryableError"
or not failure.non_retryable
):
raise
return {"order_id": order["id"], "status": "rejected"}
return {"order_id": order["id"], "status": "accepted"}
```

Con la entrada `{"id": "order-123", "amount": 0}`, la actividad falla una vez y el workflow completa normalmente con `status: rejected`. Maneje solo el fallo de negocio previsto; el ejemplo vuelve a lanzar los demás. Continúe con otros pasos durables si lo requiere su flujo. Un `ActivityFailed` no capturado hace fallar el workflow. Conservar la causa Python no significa que la política examine esa cadena ni que todos los SDK expongan un objeto de causa idéntico.

### Actividades síncronas y asíncronas {#activity-execution}

Las actividades remotas normales con `def` se ejecutan en un grupo de hilos
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": "a9fc8330cbc78953244b94a170197038a299a5a25fa78c38c7bfb451fd67d345",
"polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1",
"polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3",
"polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0",
"polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83",
Expand Down
36 changes: 36 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 @@ -1053,6 +1053,42 @@ async def validate(data: dict) -> dict:
return data
```

### Erreurs métier terminales {#terminal-business-errors}

`non_retryable_error_types` correspond exactement au nom court (`exception_type`) ou au nom de classe qualifié (`exception_class`) de l’exception **extérieure**. Par exemple, `ValidationError` ne correspond pas à `SpecificValidationError`, même si c’est une sous-classe. Un nom qualifié comme `app.errors.ValidationError` doit correspondre à la classe enregistrée. La politique ne parcourt ni les sous-classes ni les causes. `raise RuntimeError(...) from NonRetryableError(...)` signale le `RuntimeError` extérieur et peut encore être retenté.

Pour convertir une exception métier connue, levez `NonRetryableError` comme exception extérieure et conservez la cause Python d’origine avec `cause=error`. L’échec de l’activité devient non réessayable sans liste de noms. Cela ne fait pas automatiquement échouer tout le workflow : son code reçoit `ActivityFailed` quand l’activité n’a plus de tentatives, soit parce que l’erreur est non réessayable, soit parce que le budget est épuisé.

```python
from durable_workflow import ActivityFailed, NonRetryableError, activity, workflow

@activity.defn(name="validate_order")
async def validate_order(order: dict) -> dict:
try:
if order.get("amount", 0) <= 0:
raise ValueError("amount must be positive")
except ValueError as error:
raise NonRetryableError("Order validation failed", cause=error) from error
return order

@workflow.defn(name="place_order")
class PlaceOrderWorkflow:
def run(self, ctx, order: dict):
try:
yield ctx.schedule_activity("validate_order", [order])
except ActivityFailed as failure:
if (
failure.activity_type != "validate_order"
or failure.exception_type != "NonRetryableError"
or not failure.non_retryable
):
raise
return {"order_id": order["id"], "status": "rejected"}
return {"order_id": order["id"], "status": "accepted"}
```

Pour l’entrée `{"id": "order-123", "amount": 0}`, l’activité échoue une fois et le workflow termine normalement avec `status: rejected`. Traitez seulement l’échec métier prévu ; l’exemple relance les autres. Poursuivez avec d’autres étapes durables si le parcours métier l’exige. Un `ActivityFailed` non intercepté fait échouer le workflow. Conserver une cause Python ne signifie ni que la politique inspecte cette chaîne ni que tous les SDK exposent le même objet de cause.

### Activités synchrones et asynchrones {#activity-execution}

Les activités distantes ordinaires définies avec `def` s’exécutent dans un pool
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": "a9fc8330cbc78953244b94a170197038a299a5a25fa78c38c7bfb451fd67d345",
"polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1",
"polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3",
"polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0",
"polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83",
Expand Down
36 changes: 36 additions & 0 deletions i18n/ja/docusaurus-plugin-content-docs/current/polyglot/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,42 @@ async def validate(data: dict) -> dict:
return data
```

### 終端となる業務エラー {#terminal-business-errors}

`non_retryable_error_types` は、**最も外側の**例外の短い名前(`exception_type`)または完全修飾クラス名(`exception_class`)に完全一致します。例えば `ValidationError` は、サブクラスであっても `SpecificValidationError` には一致しません。`app.errors.ValidationError` のような修飾名は、記録されたクラス名に一致する必要があります。ポリシーはサブクラスや例外の原因をたどりません。`raise RuntimeError(...) from NonRetryableError(...)` は外側の `RuntimeError` を報告するため、再試行される場合があります。

既知の業務例外を変換するときは、`NonRetryableError` を外側の例外として送出し、`cause=error` で元の Python の原因を保持してください。名前の一覧に依存せず、アクティビティの失敗を再試行不可にします。これだけでワークフロー全体が自動的に失敗するわけではありません。再試行不可の失敗、または試行予算の消費によって再試行がなくなると、ワークフローコードは `ActivityFailed` を受け取ります。

```python
from durable_workflow import ActivityFailed, NonRetryableError, activity, workflow

@activity.defn(name="validate_order")
async def validate_order(order: dict) -> dict:
try:
if order.get("amount", 0) <= 0:
raise ValueError("amount must be positive")
except ValueError as error:
raise NonRetryableError("Order validation failed", cause=error) from error
return order

@workflow.defn(name="place_order")
class PlaceOrderWorkflow:
def run(self, ctx, order: dict):
try:
yield ctx.schedule_activity("validate_order", [order])
except ActivityFailed as failure:
if (
failure.activity_type != "validate_order"
or failure.exception_type != "NonRetryableError"
or not failure.non_retryable
):
raise
return {"order_id": order["id"], "status": "rejected"}
return {"order_id": order["id"], "status": "accepted"}
```

入力が `{"id": "order-123", "amount": 0}` の場合、アクティビティは一回失敗し、ワークフローは `status: rejected` で正常に完了します。意図した業務失敗だけを処理してください。この例は他の失敗を再送出します。業務フローに必要なら後続の永続ステップへ進めます。未処理の `ActivityFailed` はワークフローを失敗させます。Python の原因を保持しても、再試行ポリシーがその連鎖を検査することや、すべての SDK が同じ原因オブジェクトを提供することを意味しません。

### 同期アクティビティと非同期アクティビティ {#activity-execution}

通常のリモート `def` アクティビティは、`max_concurrent_activity_tasks` で上限を設定した専用のスレッドプールで実行されます。
Expand Down
2 changes: 1 addition & 1 deletion i18n/ja/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": "a9fc8330cbc78953244b94a170197038a299a5a25fa78c38c7bfb451fd67d345",
"polyglot/python.md": "174acc170fc749284b5115f995996885c04056340d44bd334c6cee33e288abd1",
"polyglot/rust-cloud-quickstart.md": "2303434fc9302a81a8381ad4836423ef9952eb3201a4574c432254e3cb68c5f3",
"polyglot/rust.md": "32de05a771b5ee6d8cf54fdd408222372995ce29c8be63102abe58b977ac8ef0",
"polyglot/server-api-reference.md": "e94af77a13b341d6fdc9941d4ee94e5053ab932d2e77069b238eb33351414e83",
Expand Down
Loading
Loading