Skip to content

Commit 79baa15

Browse files
authored
[3/5] Use dataclass_transform to prevent type errors in service definition (#15)
1 parent c8d54f8 commit 79baa15

8 files changed

+41
-37
lines changed

src/nexusrpc/_service.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
overload,
2020
)
2121

22+
from typing_extensions import dataclass_transform
23+
2224
from nexusrpc._common import InputT, OutputT, ServiceT
2325
from nexusrpc._util import (
2426
get_annotations,
@@ -69,10 +71,12 @@ def _validation_errors(self) -> list[str]:
6971

7072

7173
@overload
74+
@dataclass_transform(field_specifiers=(Operation,))
7275
def service(cls: type[ServiceT]) -> type[ServiceT]: ...
7376

7477

7578
@overload
79+
@dataclass_transform(field_specifiers=(Operation,))
7680
def service(
7781
*, name: Optional[str] = None
7882
) -> Callable[[type[ServiceT]], type[ServiceT]]: ...

tests/handler/test_invalid_usage.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class OperationHandlerOverridesNameInconsistentlyWithServiceDefinition(_TestCase
3434
def build():
3535
@nexusrpc.service
3636
class SD:
37-
my_op: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
37+
my_op: nexusrpc.Operation[None, None]
3838

3939
@service_handler(service=SD)
4040
class SH:
@@ -49,8 +49,8 @@ class ServiceDefinitionHasExtraOp(_TestCase):
4949
def build():
5050
@nexusrpc.service
5151
class SD:
52-
my_op_1: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
53-
my_op_2: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
52+
my_op_1: nexusrpc.Operation[None, None]
53+
my_op_2: nexusrpc.Operation[None, None]
5454

5555
@service_handler(service=SD)
5656
class SH:
@@ -67,7 +67,7 @@ class ServiceHandlerHasExtraOp(_TestCase):
6767
def build():
6868
@nexusrpc.service
6969
class SD:
70-
my_op_1: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
70+
my_op_1: nexusrpc.Operation[None, None]
7171

7272
@service_handler(service=SD)
7373
class SH:

tests/handler/test_request_routing.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async def _op_impl(self, ctx: StartOperationContext, input: None) -> bool:
4242
class NoOverrides(_TestCase):
4343
@nexusrpc.service
4444
class UserService:
45-
op: nexusrpc.Operation[None, bool] # type: ignore[reportUninitializedInstanceVariable]
45+
op: nexusrpc.Operation[None, bool]
4646

4747
@service_handler(service=UserService)
4848
class UserServiceHandler(_TestCase.UserServiceHandler):
@@ -56,7 +56,7 @@ async def op(self, ctx: StartOperationContext, input: None) -> bool:
5656
class OverrideServiceName(_TestCase):
5757
@nexusrpc.service(name="UserService-renamed")
5858
class UserService:
59-
op: nexusrpc.Operation[None, bool] # type: ignore[reportUninitializedInstanceVariable]
59+
op: nexusrpc.Operation[None, bool]
6060

6161
@service_handler(service=UserService)
6262
class UserServiceHandler(_TestCase.UserServiceHandler):
@@ -70,7 +70,7 @@ async def op(self, ctx: StartOperationContext, input: None) -> bool:
7070
class OverrideOperationName(_TestCase):
7171
@nexusrpc.service
7272
class UserService:
73-
op: nexusrpc.Operation[None, bool] = nexusrpc.Operation(name="op-renamed") # type: ignore[reportUninitializedInstanceVariable]
73+
op: nexusrpc.Operation[None, bool] = nexusrpc.Operation(name="op-renamed")
7474

7575
@service_handler(service=UserService)
7676
class UserServiceHandler(_TestCase.UserServiceHandler):
@@ -84,7 +84,7 @@ async def op(self, ctx: StartOperationContext, input: None) -> bool:
8484
class OverrideServiceAndOperationName(_TestCase):
8585
@nexusrpc.service(name="UserService-renamed")
8686
class UserService:
87-
op: nexusrpc.Operation[None, bool] = nexusrpc.Operation(name="op-renamed") # type: ignore[reportUninitializedInstanceVariable]
87+
op: nexusrpc.Operation[None, bool] = nexusrpc.Operation(name="op-renamed")
8888

8989
@service_handler(service=UserService)
9090
class UserServiceHandler(_TestCase.UserServiceHandler):

tests/handler/test_service_handler_decorator_collects_expected_operation_definitions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def sync_operation_handler(
105105
class ManualOperationWithContract(_TestCase):
106106
@nexusrpc.service
107107
class Contract:
108-
operation: nexusrpc.Operation[Input, Output] # type: ignore[reportUninitializedInstanceVariable]
108+
operation: nexusrpc.Operation[Input, Output]
109109

110110
@service_handler(service=Contract)
111111
class Service:

tests/handler/test_service_handler_decorator_requirements.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class _DecoratorValidationTestCase(_TestCase):
2929
class MissingOperationFromDefinition(_DecoratorValidationTestCase):
3030
@nexusrpc.service
3131
class UserService:
32-
op_A: nexusrpc.Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
33-
op_B: nexusrpc.Operation[bool, float] # type: ignore[reportUninitializedInstanceVariable]
32+
op_A: nexusrpc.Operation[int, str]
33+
op_B: nexusrpc.Operation[bool, float]
3434

3535
class UserServiceHandler:
3636
@operation_handler
@@ -44,7 +44,7 @@ def op_A(self) -> OperationHandler[int, str]: ...
4444
class MethodNameDoesNotMatchDefinition(_DecoratorValidationTestCase):
4545
@nexusrpc.service
4646
class UserService:
47-
op_A: nexusrpc.Operation[int, str] = nexusrpc.Operation(name="foo") # type: ignore[reportUninitializedInstanceVariable]
47+
op_A: nexusrpc.Operation[int, str] = nexusrpc.Operation(name="foo")
4848

4949
class UserServiceHandler:
5050
@operation_handler
@@ -81,12 +81,12 @@ class ServiceHandlerInheritanceWithServiceDefinition(
8181
):
8282
@nexusrpc.service
8383
class BaseUserService:
84-
base_op: nexusrpc.Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
84+
base_op: nexusrpc.Operation[int, str]
8585

8686
@nexusrpc.service
8787
class UserService:
88-
base_op: nexusrpc.Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
89-
child_op: nexusrpc.Operation[bool, float] # type: ignore[reportUninitializedInstanceVariable]
88+
base_op: nexusrpc.Operation[int, str]
89+
child_op: nexusrpc.Operation[bool, float]
9090

9191
@service_handler(service=BaseUserService)
9292
class BaseUserServiceHandler:
@@ -139,11 +139,11 @@ class _ServiceDefinitionInheritanceTestCase(_TestCase):
139139
class ServiceDefinitionInheritance(_ServiceDefinitionInheritanceTestCase):
140140
@nexusrpc.service
141141
class BaseUserService:
142-
op_from_base_definition: nexusrpc.Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
142+
op_from_base_definition: nexusrpc.Operation[int, str]
143143

144144
@nexusrpc.service
145145
class UserService(BaseUserService):
146-
op_from_child_definition: nexusrpc.Operation[bool, float] # type: ignore[reportUninitializedInstanceVariable]
146+
op_from_child_definition: nexusrpc.Operation[bool, float]
147147

148148
expected_ops = {
149149
"op_from_base_definition",

tests/handler/test_service_handler_decorator_validates_against_service_contract.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class _InterfaceImplementationTestCase(_BaseTestCase):
2626
class ValidImpl(_InterfaceImplementationTestCase):
2727
@nexusrpc.service
2828
class Interface:
29-
op: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
29+
op: nexusrpc.Operation[None, None]
3030

3131
def unrelated_method(self) -> None: ...
3232

@@ -54,7 +54,7 @@ def unrelated_method(self) -> None: ...
5454
class ValidImplWithoutTypeAnnotations(_InterfaceImplementationTestCase):
5555
@nexusrpc.service
5656
class Interface:
57-
op: nexusrpc.Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
57+
op: nexusrpc.Operation[int, str]
5858

5959
with warnings.catch_warnings(record=True) as _warnings:
6060
warnings.simplefilter("always")
@@ -74,7 +74,7 @@ async def op(self, ctx, input): ... # type: ignore[reportMissingParameterType]
7474
class MissingOperation(_InterfaceImplementationTestCase):
7575
@nexusrpc.service
7676
class Interface:
77-
op: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
77+
op: nexusrpc.Operation[None, None]
7878

7979
class Impl:
8080
pass
@@ -85,7 +85,7 @@ class Impl:
8585
class MissingInputAnnotation(_InterfaceImplementationTestCase):
8686
@nexusrpc.service
8787
class Interface:
88-
op: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
88+
op: nexusrpc.Operation[None, None]
8989

9090
with warnings.catch_warnings(record=True) as _warnings:
9191
warnings.simplefilter("always")
@@ -105,7 +105,7 @@ async def op(self, ctx: StartOperationContext, input) -> None: ... # type: igno
105105
class MissingContextAnnotation(_InterfaceImplementationTestCase):
106106
@nexusrpc.service
107107
class Interface:
108-
op: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
108+
op: nexusrpc.Operation[None, None]
109109

110110
with warnings.catch_warnings(record=True) as _warnings:
111111
warnings.simplefilter("always")
@@ -125,7 +125,7 @@ async def op(self, ctx, input: None) -> None: ... # type: ignore[reportMissingP
125125
class WrongOutputType(_InterfaceImplementationTestCase):
126126
@nexusrpc.service
127127
class Interface:
128-
op: nexusrpc.Operation[None, int] # type: ignore[reportUninitializedInstanceVariable]
128+
op: nexusrpc.Operation[None, int]
129129

130130
class Impl:
131131
@sync_operation
@@ -137,7 +137,7 @@ async def op(self, ctx: StartOperationContext, input: None) -> str: ...
137137
class WrongOutputTypeWithNone(_InterfaceImplementationTestCase):
138138
@nexusrpc.service
139139
class Interface:
140-
op: nexusrpc.Operation[str, None] # type: ignore[reportUninitializedInstanceVariable]
140+
op: nexusrpc.Operation[str, None]
141141

142142
class Impl:
143143
@sync_operation
@@ -149,7 +149,7 @@ async def op(self, ctx: StartOperationContext, input: str) -> str: ...
149149
class ValidImplWithNone(_InterfaceImplementationTestCase):
150150
@nexusrpc.service
151151
class Interface:
152-
op: nexusrpc.Operation[str, None] # type: ignore[reportUninitializedInstanceVariable]
152+
op: nexusrpc.Operation[str, None]
153153

154154
class Impl:
155155
@sync_operation
@@ -161,7 +161,7 @@ async def op(self, ctx: StartOperationContext, input: str) -> None: ...
161161
class MoreSpecificImplAllowed(_InterfaceImplementationTestCase):
162162
@nexusrpc.service
163163
class Interface:
164-
op: nexusrpc.Operation[Any, Any] # type: ignore[reportUninitializedInstanceVariable]
164+
op: nexusrpc.Operation[Any, Any]
165165

166166
class Impl:
167167
@sync_operation
@@ -185,7 +185,7 @@ class Subclass(SuperClass):
185185
class OutputCovarianceImplOutputCanBeSameType(_InterfaceImplementationTestCase):
186186
@nexusrpc.service
187187
class Interface:
188-
op: nexusrpc.Operation[X, X] # type: ignore[reportUninitializedInstanceVariable]
188+
op: nexusrpc.Operation[X, X]
189189

190190
class Impl:
191191
@sync_operation
@@ -197,7 +197,7 @@ async def op(self, ctx: StartOperationContext, input: X) -> X: ...
197197
class OutputCovarianceImplOutputCanBeSubclass(_InterfaceImplementationTestCase):
198198
@nexusrpc.service
199199
class Interface:
200-
op: nexusrpc.Operation[X, SuperClass] # type: ignore[reportUninitializedInstanceVariable]
200+
op: nexusrpc.Operation[X, SuperClass]
201201

202202
class Impl:
203203
@sync_operation
@@ -211,7 +211,7 @@ class OutputCovarianceImplOutputCannnotBeStrictSuperclass(
211211
):
212212
@nexusrpc.service
213213
class Interface:
214-
op: nexusrpc.Operation[X, Subclass] # type: ignore[reportUninitializedInstanceVariable]
214+
op: nexusrpc.Operation[X, Subclass]
215215

216216
class Impl:
217217
@sync_operation
@@ -223,7 +223,7 @@ async def op(self, ctx: StartOperationContext, input: X) -> SuperClass: ...
223223
class InputContravarianceImplInputCanBeSameType(_InterfaceImplementationTestCase):
224224
@nexusrpc.service
225225
class Interface:
226-
op: nexusrpc.Operation[X, X] # type: ignore[reportUninitializedInstanceVariable]
226+
op: nexusrpc.Operation[X, X]
227227

228228
class Impl:
229229
@sync_operation
@@ -235,7 +235,7 @@ async def op(self, ctx: StartOperationContext, input: X) -> X: ...
235235
class InputContravarianceImplInputCanBeSuperclass(_InterfaceImplementationTestCase):
236236
@nexusrpc.service
237237
class Interface:
238-
op: nexusrpc.Operation[Subclass, X] # type: ignore[reportUninitializedInstanceVariable]
238+
op: nexusrpc.Operation[Subclass, X]
239239

240240
class Impl:
241241
@sync_operation
@@ -247,7 +247,7 @@ async def op(self, ctx: StartOperationContext, input: SuperClass) -> X: ...
247247
class InputContravarianceImplInputCannotBeSubclass(_InterfaceImplementationTestCase):
248248
@nexusrpc.service
249249
class Interface:
250-
op: nexusrpc.Operation[SuperClass, X] # type: ignore[reportUninitializedInstanceVariable]
250+
op: nexusrpc.Operation[SuperClass, X]
251251

252252
class Impl:
253253
@sync_operation
@@ -297,7 +297,7 @@ def test_service_decorator_enforces_interface_implementation(
297297
def test_service_does_not_implement_operation_name():
298298
@nexusrpc.service
299299
class Contract:
300-
operation_a: nexusrpc.Operation[None, None] # type: ignore[reportUninitializedInstanceVariable]
300+
operation_a: nexusrpc.Operation[None, None]
301301

302302
class Service:
303303
@sync_operation

tests/service_definition/test_service_decorator_creates_expected_operation_declaration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ class OperationDeclarationTestCase(_BaseTestCase):
2424
class OperationDeclarations(OperationDeclarationTestCase):
2525
@nexusrpc.service
2626
class Interface:
27-
a: nexusrpc.Operation[None, Output] # type: ignore[reportUninitializedInstanceVariable]
28-
b: nexusrpc.Operation[int, str] = nexusrpc.Operation(name="b-name") # type: ignore[reportUninitializedInstanceVariable]
27+
a: nexusrpc.Operation[None, Output]
28+
b: nexusrpc.Operation[int, str] = nexusrpc.Operation(name="b-name")
2929

3030
expected_ops = {
3131
"a": (type(None), Output),

tests/service_definition/test_service_definition_inheritance.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class _TestCase(_BaseTestCase):
2929
class TypeAnnotationsOnly(_TestCase):
3030
@nexusrpc.service
3131
class A1:
32-
a: Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
32+
a: Operation[int, str]
3333

3434
# TODO(preview) why is the decorator omitted here?
3535
class A2(A1):
@@ -100,7 +100,7 @@ class A1:
100100
class ChildClassSynthesizedWithTypeValues(_TestCase):
101101
@nexusrpc.service
102102
class A1:
103-
a: Operation[int, str] # type: ignore[reportUninitializedInstanceVariable]
103+
a: Operation[int, str]
104104

105105
A2 = type("A2", (A1,), {name: Operation[int, str] for name in ["b"]})
106106

0 commit comments

Comments
 (0)