Skip to content
Open
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
4 changes: 3 additions & 1 deletion python/restate/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def handler(
Callable: The decorated function.

Raises:
ValueError: If the handler name is not provided.
ValueError: If the handler name is not provided, or a handler with the same name is already registered.

Example:
@service.handler()
Expand Down Expand Up @@ -215,6 +215,8 @@ def wrapped(*args, **kwargs):
invocation_retry_policy,
combined_context_managers,
)
if handler.name in self.handlers:
raise ValueError(f"Handler {handler.name} already exists in {self.name}")
self.handlers[handler.name] = handler
return wrapped

Expand Down
4 changes: 3 additions & 1 deletion python/restate/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def handler(
Callable: The decorated function.

Raises:
ValueError: If the handler name is not provided.
ValueError: If the handler name is not provided, or a handler with the same name is already registered.

Example:
@service.handler()
Expand Down Expand Up @@ -206,6 +206,8 @@ def wrapped(*args, **kwargs):
invocation_retry_policy,
combined_context_managers,
)
if handler.name in self.handlers:
raise ValueError(f"Handler {handler.name} already exists in {self.name}")
self.handlers[handler.name] = handler
return wrapped

Expand Down
4 changes: 3 additions & 1 deletion python/restate/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ def _add_handler(
Callable: The decorated function.

Raises:
ValueError: If the handler name is not provided.
ValueError: If the handler name is not provided, or a handler with the same name is already registered.

Example:
@service.handler()
Expand Down Expand Up @@ -383,6 +383,8 @@ def wrapped(*args, **kwargs):
invocation_retry_policy=invocation_retry_policy,
context_managers=combined_context_managers,
)
if handler.name in self.handlers:
raise ValueError(f"Handler {handler.name} already exists in {self.name}")
self.handlers[handler.name] = handler
return wrapped

Expand Down
54 changes: 54 additions & 0 deletions tests/duplicate_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#
# Copyright (c) 2023-2026 - Restate Software, Inc., Restate GmbH
#
# This file is part of the Restate SDK for Python,
# which is released under the MIT license.
#
# You can find a copy of the license in file LICENSE in the root
# directory of this repository or package, or at
# https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
#
import pytest
import restate


def test_service_rejects_duplicate_handler_name():
service = restate.Service("Greeter")

@service.handler("greet")
async def first(ctx: restate.Context) -> str:
return "first"

with pytest.raises(ValueError, match="Handler greet already exists in Greeter"):

@service.handler("greet")
async def second(ctx: restate.Context) -> str:
return "second"


def test_virtual_object_rejects_duplicate_handler_name():
obj = restate.VirtualObject("Counter")

@obj.handler("add")
async def first(ctx: restate.ObjectContext) -> int:
return 1

with pytest.raises(ValueError, match="Handler add already exists in Counter"):

@obj.handler("add")
async def second(ctx: restate.ObjectContext) -> int:
return 2


def test_workflow_rejects_duplicate_handler_name():
workflow = restate.Workflow("Signup")

@workflow.main("run")
async def run(ctx: restate.WorkflowContext) -> str:
return "run"

with pytest.raises(ValueError, match="Handler run already exists in Signup"):

@workflow.handler("run")
async def other(ctx: restate.WorkflowSharedContext) -> str:
return "other"
Loading