Skip to content

explicit_recovery as a handler and service key - #265

Merged
igalshilman merged 6 commits into
mainfrom
explicit-recovery-key
Nov 4, 2026
Merged

igalshilman merged 6 commits into
mainfrom
explicit-recovery-key

Conversation

@igalshilman

@igalshilman igalshilman commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

explicit_recovery as a handler and service key

This makes the preview explicit_recovery option a key, like every other option and like explicit_cancellation (#264), instead of a decorator:

@counter.handler(explicit_recovery=True)                    # one handler
async def add(ctx: ObjectContext, n: int) -> None: ...

counter = VirtualObject("counter", explicit_recovery=True)  # all of its handlers
  • The key:
    • Exists on handler() and a workflow's main(), and on the Service, VirtualObject and Workflow constructors. Off by default.
    • Its docs mark it as preview and state what explicit recovery means for a handler: each attempt's calls and sends are new requests (give them an idempotency key that is the same on every attempt), ctx.run runs again on every attempt, and awakeables are not supported.
  • The decorator is removed. restate._preview.explicit_recovery was never released, so it's deleted rather than deprecated.
  • tests/explicit_recovery.py uses the key.

🤖 Generated with Claude Code

igalshilman and others added 3 commits November 4, 2026 18:59
With explicit cancellation, Restate cancelling an invocation neither fails
the handler's awaits nor cancels the calls it made: the handler finds out
by awaiting cancellation(ctx), and decides what to stop.

    @explicit_cancellation
    @greeter.handler()
    async def greet(ctx, name):
        done, _ = await restate.select(work=ctx.service_call(slow, arg=name), cancelled=cancellation(ctx))
        if done == "cancelled":
            await cancel_previous_calls(ctx)   # the calls still in flight
            raise TerminalError("cancelled", status_code=409)

In restate._preview, next to explicit_recovery: on a handler, or a whole
service, object or workflow. The VM disables shared-core's implicit
cancellation; the context keeps the attempt's calls that may be in flight
(only with the option on), which cancel_previous_calls cancels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RePHkTUvGceAzHBUPULRvy
Like every other option: @svc.handler(explicit_cancellation=True), a
workflow's main()/handler() too, and Service/VirtualObject/Workflow(...,
explicit_cancellation=True) for all their handlers. The
restate._preview.explicit_cancellation decorator is gone;
cancellation() and cancel_previous_calls() stay in restate._preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RePHkTUvGceAzHBUPULRvy
Like explicit_cancellation and every other option:
@svc.handler(explicit_recovery=True), a workflow's main()/handler() too,
and Service/VirtualObject/Workflow(..., explicit_recovery=True) for all
their handlers. The restate._preview.explicit_recovery decorator still
works, and warns that it is deprecated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RePHkTUvGceAzHBUPULRvy

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — two documentation rough edges inline.

Reviewed changes

This review covers the single commit that turns preview explicit_recovery into a handler and service option and deprecates the decorator.

  • New option on all constructors and decorators — explicit_recovery: bool = False added to the Service, VirtualObject and Workflow constructors, their handler(), Workflow.main()/_add_handler, and make_handler. It's passed by keyword, so the positional make_handler argument lists stay intact. The runtime still ORs the handler flag with the service flag in server.py, so service-level enabling works unchanged.
  • Decorator deprecated — restate._preview.explicit_recovery still sets the flag but now emits a DeprecationWarning with stacklevel=2.
  • Tests migrated — tests/explicit_recovery.py now uses the keyword option. It also checks that the decorator still works and warns.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread python/restate/service.py Resolved
Comment thread python/restate/_preview.py Resolved
Base automatically changed from explicit-cancellation to main November 4, 2026 18:23
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Test Results

  8 files  ±0    8 suites  ±0   3m 51s ⏱️ +3s
 71 tests ±0   71 ✅ ±0  0 💤 ±0  0 ❌ ±0 
322 runs  ±0  322 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit c6082d5. ± Comparison against base commit 5f2ab58.

…d:: directive

The handler decorators' explicit_recovery docs say what explicit recovery
means for calls and sends (new requests each attempt: give them a stable
idempotency key), ctx.run (runs again) and awakeables (not supported), as
the decorator's docstring did. The deprecated decorator keeps that text,
with a .. deprecated:: 1.4.0 directive, as connection.py's Connection.wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RePHkTUvGceAzHBUPULRvy

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. Both earlier documentation threads are addressed.

Reviewed changes

Since the last Pullfrog review, one commit changed docstrings only. There are no code or behavior changes.

  • Restored the explicit-recovery caveats — the explicit_recovery docs on handler() for Service, VirtualObject and Workflow, and on Workflow.main(), now cover all three caveats: calls and sends are new requests on each attempt (so give them a stable idempotency key, e.g. one made from ctx.request().id), ctx.run runs again on every attempt, and awakeables are unsupported. The Handler field doc points readers to those docs.
  • Added the .. deprecated:: 1.4.0 directive — the deprecated restate._preview.explicit_recovery docstring now follows the repo's deprecation convention, and it keeps the full caveat list.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

restate._preview.explicit_recovery was never released, so it goes rather
than being deprecated: explicit_recovery=True on the handler decorator, or
on the service, is the way to enable explicit recovery (still a preview
feature, as its docs say).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@igalshilman igalshilman changed the title explicit_recovery as a handler and service key (deprecate the decorator) explicit_recovery as a handler and service key Nov 4, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The code changes are clean, with one note below about the PR description.

Reviewed changes

Since the last Pullfrog review, one commit deletes the explicit_recovery decorator. The handler and service keys are now the only way to turn the option on.

  • Removed restate._preview.explicit_recovery. _preview.py now holds only the explicit-cancellation helpers from #264. The imports that only the decorator used (handler_from_callable, Service, VirtualObject, Workflow, warnings) and the T TypeVar are gone. Nothing else in the repo references the decorator.
  • Updated tests/explicit_recovery.py. Removed the deprecation-warning test and the misapplied-decorator ValueError test. Added assertions that the Service and Workflow constructors accept the key.

ℹ️ The removed decorator was in published releases

The PR description says restate._preview.explicit_recovery "was never released". It was: the restate-sdk 1.3.0 sdist on PyPI contains python/restate/_preview.py with def explicit_recovery, and the decorator was already in the tree at the 1.1.0 version bump. After this change, any 1.3.0 user who has from restate._preview import explicit_recovery gets an ImportError when they upgrade.

The _preview module says its APIs "might change or be removed without notice", so deleting the decorator is allowed. The decision just shouldn't rest on the claim that it never shipped.

Technical details
# Removal of a released `_preview` API

## Affected sites
- python/restate/_preview.py — `explicit_recovery` decorator deleted (shipped in restate-sdk 1.1.0–1.3.0 on PyPI)
- PR #265 description — "was never released, so it's deleted rather than deprecated"

## Required outcome
- The PR description and release notes describe the removal accurately, or the deprecated shim from 7365129 is kept for a release.

## Open questions for the human
- Is a hard removal fine under the `_preview` contract, or should 7365129's `DeprecationWarning` shim stay for one release (1.4.0) and be removed after that?

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

# Conflicts:
#	python/restate/_preview.py
#	python/restate/handler.py
#	python/restate/object.py
#	python/restate/service.py
#	python/restate/workflow.py
@igalshilman
igalshilman merged commit 39bb159 into main Nov 4, 2026
9 of 10 checks passed
@igalshilman
igalshilman deleted the explicit-recovery-key branch November 4, 2026 18:35
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant