Skip to content

Commit 0f1cf25

Browse files
Prevent setting method_name in Operation constructor (#41)
The method_name field should be automatically set by the framework to match the attribute name in service definitions or the method name in operation handlers. Users should not be able to set it directly. Changes: - Add init=False to method_name field in Operation dataclass - Update framework code to set method_name after Operation construction - Keep validation to catch manual method_name assignment that doesn't match the attribute name - Remove test case for duplicate method names via constructor (that specific scenario is no longer possible) - Update test expectations to not pass method_name to constructor Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4f7b9d4 commit 0f1cf25

File tree

4 files changed

+21
-58
lines changed

4 files changed

+21
-58
lines changed

src/nexusrpc/_service.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ class MyNexusService:
4646
"""
4747

4848
name: str
49-
# TODO(preview): they should not be able to set method_name in constructor
50-
method_name: Optional[str] = dataclasses.field(default=None)
49+
method_name: Optional[str] = dataclasses.field(default=None, init=False)
5150
input_type: Optional[type[InputT]] = dataclasses.field(default=None)
5251
output_type: Optional[type[OutputT]] = dataclasses.field(default=None)
5352

@@ -143,10 +142,10 @@ def decorator(cls: type[ServiceT]) -> type[ServiceT]:
143142
if not hasattr(cls, op_name):
144143
op = Operation(
145144
name=op_defn.name,
146-
method_name=op_defn.method_name,
147145
input_type=op_defn.input_type,
148146
output_type=op_defn.output_type,
149147
)
148+
op.method_name = op_defn.method_name
150149
setattr(cls, op_name, op)
151150

152151
return cls
@@ -287,7 +286,6 @@ def _collect_operations(
287286
# my_op: Operation[I, O]
288287
op = operations[key] = Operation(
289288
name=key,
290-
method_name=key,
291289
input_type=input_type,
292290
output_type=output_type,
293291
)
@@ -311,15 +309,14 @@ def _collect_operations(
311309
# It looked like
312310
# my_op = Operation(...)
313311
op = operations[key]
314-
if not op.method_name:
315-
op.method_name = key
316-
elif op.method_name != key:
317-
raise ValueError(
318-
f"Operation {key} method_name ({op.method_name}) must match attribute name {key}"
319-
)
320312

321-
if op.method_name is None:
322-
op.method_name = key
313+
# Validate that if method_name was set (via direct assignment after
314+
# construction), it matches the attribute name
315+
if op.method_name is not None and op.method_name != key:
316+
raise ValueError(
317+
f"Operation {key} method_name ({op.method_name}) must match attribute name {key}"
318+
)
319+
op.method_name = key
323320

324321
op_defns = {}
325322
for op in operations.values():

src/nexusrpc/handler/_decorators.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,13 @@ def decorator(
174174
f"but operation {method.__name__} has {len(type_args)} type parameters: {type_args}"
175175
)
176176

177-
set_operation(
178-
method,
179-
Operation(
180-
name=name or method.__name__,
181-
method_name=method.__name__,
182-
input_type=input_type,
183-
output_type=output_type,
184-
),
177+
op: Operation[Any, Any] = Operation(
178+
name=name or method.__name__,
179+
input_type=input_type,
180+
output_type=output_type,
185181
)
182+
op.method_name = method.__name__
183+
set_operation(method, op)
186184
return method
187185

188186
if method is None:
@@ -278,15 +276,13 @@ def _start(ctx: StartOperationContext, input: Any) -> Any:
278276
)
279277

280278
method_name = get_callable_name(start)
281-
set_operation(
282-
operation_handler_factory,
283-
Operation(
284-
name=name or method_name,
285-
method_name=method_name,
286-
input_type=input_type,
287-
output_type=output_type,
288-
),
279+
op = Operation(
280+
name=name or method_name,
281+
input_type=input_type,
282+
output_type=output_type,
289283
)
284+
op.method_name = method_name
285+
set_operation(operation_handler_factory, op)
290286

291287
set_operation_factory(start, operation_handler_factory)
292288
return start

tests/handler/test_invalid_usage.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -124,29 +124,6 @@ def my_op(self, _ctx: StartOperationContext, _input: None) -> None: ...
124124
error_message = "you have not supplied an executor"
125125

126126

127-
class ServiceDefinitionHasDuplicateMethodNames(_TestCase):
128-
@staticmethod
129-
def build():
130-
@nexusrpc.service
131-
class SD:
132-
my_op: nexusrpc.Operation[None, None] = nexusrpc.Operation(
133-
name="my_op",
134-
method_name="my_op",
135-
input_type=None,
136-
output_type=None,
137-
)
138-
my_op_2: nexusrpc.Operation[None, None] = nexusrpc.Operation(
139-
name="my_op_2",
140-
method_name="my_op",
141-
input_type=None,
142-
output_type=None,
143-
)
144-
145-
_ = SD
146-
147-
error_message = "Operation method name 'my_op' is not unique"
148-
149-
150127
class OperationHandlerNoInputOutputTypeAnnotationsWithoutServiceDefinition(_TestCase):
151128
@staticmethod
152129
def build():
@@ -189,7 +166,6 @@ async def op(self, ctx, input): ... # type: ignore[reportMissingParameterType]
189166
ServiceDefinitionHasExtraOp,
190167
ServiceHandlerHasExtraOp,
191168
AsyncioHandlerWithSyncioOperation,
192-
ServiceDefinitionHasDuplicateMethodNames,
193169
OperationHandlerNoInputOutputTypeAnnotationsWithoutServiceDefinition,
194170
SyncOperationNoTypeAnnotationsWithoutServiceDefinition,
195171
],

tests/handler/test_service_handler_decorator_collects_expected_operation_definitions.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def operation(self) -> OperationHandler[Input, Output]: ...
4343
expected_operations = {
4444
"operation": nexusrpc.Operation(
4545
name="operation",
46-
method_name="operation",
4746
input_type=Input,
4847
output_type=Output,
4948
),
@@ -59,7 +58,6 @@ def operation(self) -> OperationHandler[Input, Output]: ...
5958
expected_operations = {
6059
"operation": nexusrpc.Operation(
6160
name="operation-name",
62-
method_name="operation",
6361
input_type=Input,
6462
output_type=Output,
6563
),
@@ -77,7 +75,6 @@ def sync_operation_handler(
7775
expected_operations = {
7876
"sync_operation_handler": nexusrpc.Operation(
7977
name="sync_operation_handler",
80-
method_name="sync_operation_handler",
8178
input_type=Input,
8279
output_type=Output,
8380
),
@@ -95,7 +92,6 @@ def sync_operation_handler(
9592
expected_operations = {
9693
"sync_operation_handler": nexusrpc.Operation(
9794
name="sync-operation-name",
98-
method_name="sync_operation_handler",
9995
input_type=Input,
10096
output_type=Output,
10197
),
@@ -115,7 +111,6 @@ def operation(self) -> OperationHandler[Input, Output]: ...
115111
expected_operations = {
116112
"operation": nexusrpc.Operation(
117113
name="operation",
118-
method_name="operation",
119114
input_type=Input,
120115
output_type=Output,
121116
),
@@ -137,7 +132,6 @@ def operation(self) -> OperationHandler[Input, Output]: ...
137132
expected_operations = {
138133
"operation": nexusrpc.Operation(
139134
name="operation-override",
140-
method_name="operation",
141135
input_type=Input,
142136
output_type=Output,
143137
),

0 commit comments

Comments
 (0)