Skip to content

Commit 7673bf3

Browse files
committed
Support comments and deprecation for methods and enum values
* Express methods as properties, allows for deprecation * Express enum values as properties, allows for deprecation Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
1 parent 38362fb commit 7673bf3

31 files changed

+1868
-1289
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- Add `_WhichOneofArgType_<oneof_name>` and `_WhichOneofReturnType_<oneof_name>` type aliases
1313
- Use `__new__` overloads for async stubs instead of `TypeVar` based `__init__` overloads.
1414
- https://github.com/nipunn1313/mypy-protobuf/issues/707
15+
- Export stub methods as properties instead of attributes. This allows for deprecation
16+
- Export enum fields as properties on class level (not module level) enums. This allows for deprecation
1517

1618
## 3.7.0
1719

mypy_protobuf/main.py

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ def _get_comments(self, scl: SourceCodeLocation) -> List[str]:
284284

285285
return lines
286286

287-
def _write_deprecation_warning(self, scl: SourceCodeLocation, default_message: str) -> None:
287+
def _get_deprecation_message(self, scl: SourceCodeLocation, default_message: str) -> str:
288288
msg = default_message
289289
if not self.use_default_depreaction_warnings and (comments := self._get_comments(scl)):
290290
# Make sure the comment string is a valid python string literal
@@ -296,6 +296,10 @@ def _write_deprecation_warning(self, scl: SourceCodeLocation, default_message: s
296296
except SyntaxError as e:
297297
print(f"Warning: Deprecation comment {joined} could not be parsed as a python string literal. Using default deprecation message. {e}", file=sys.stderr)
298298
pass
299+
return msg
300+
301+
def _write_deprecation_warning(self, scl: SourceCodeLocation, default_message: str) -> None:
302+
msg = self._get_deprecation_message(scl, default_message)
299303
self._write_line(
300304
'@{}("""{}""")',
301305
self._import("warnings", "deprecated"),
@@ -356,16 +360,33 @@ def write_enum_values(
356360
values: Iterable[Tuple[int, d.EnumValueDescriptorProto]],
357361
value_type: str,
358362
scl_prefix: SourceCodeLocation,
363+
*,
364+
as_properties: bool = False,
359365
) -> None:
360366
for i, val in values:
361367
if val.name in PYTHON_RESERVED:
362368
continue
363369

364370
scl = scl_prefix + [i]
365-
self._write_line(
366-
f"{val.name}: {value_type} # {val.number}",
367-
)
368-
self._write_comments(scl)
371+
# Class level
372+
if as_properties:
373+
self._write_line("@property")
374+
if val.options.deprecated:
375+
self._write_deprecation_warning(
376+
scl + [d.EnumValueDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.EnumOptions.DEPRECATED_FIELD_NUMBER],
377+
"This enum value has been marked as deprecated using proto enum value options.",
378+
)
379+
self._write_line(
380+
f"def {val.name}(self) -> {value_type}: {'' if self._has_comments(scl) else '...'} # {val.number}",
381+
)
382+
with self._indent():
383+
self._write_comments(scl)
384+
# Module level
385+
else:
386+
self._write_line(
387+
f"{val.name}: {value_type} # {val.number}",
388+
)
389+
self._write_comments(scl)
369390

370391
def write_module_attributes(self) -> None:
371392
wl = self._write_line
@@ -412,6 +433,7 @@ def write_enums(
412433
[(i, v) for i, v in enumerate(enum.value) if v.name not in PROTO_ENUM_RESERVED],
413434
value_type_helper_fq,
414435
scl + [d.EnumDescriptorProto.VALUE_FIELD_NUMBER],
436+
as_properties=True,
415437
)
416438
wl("")
417439

@@ -430,6 +452,7 @@ def write_enums(
430452
if prefix == "":
431453
wl("")
432454

455+
# Write the module level constants for enum values
433456
self.write_enum_values(
434457
enumerate(enum.value),
435458
value_type_fq,
@@ -811,7 +834,7 @@ def write_grpc_async_hacks(self) -> None:
811834
wl("...")
812835
wl("")
813836

814-
def write_grpc_stub_methods(self, service: d.ServiceDescriptorProto, scl_prefix: SourceCodeLocation, *, is_async: bool, both: bool = False, ignore_assignment_errors: bool = False) -> None:
837+
def write_grpc_stub_methods(self, service: d.ServiceDescriptorProto, scl_prefix: SourceCodeLocation, *, is_async: bool, both: bool = False, ignore_override_errors: bool = False) -> None:
815838
wl = self._write_line
816839
methods = [(i, m) for i, m in enumerate(service.method) if m.name not in PYTHON_RESERVED]
817840
if not methods:
@@ -822,17 +845,20 @@ def type_str(method: d.MethodDescriptorProto, is_async: bool) -> str:
822845

823846
for i, method in methods:
824847
scl = scl_prefix + [d.ServiceDescriptorProto.METHOD_FIELD_NUMBER, i]
825-
if both:
826-
wl(
827-
"{}: {}[{}, {}]",
828-
method.name,
829-
self._import("typing", "Union"),
830-
type_str(method, is_async=False),
831-
type_str(method, is_async=True),
848+
wl("@property")
849+
if method.options.deprecated:
850+
self._write_deprecation_warning(
851+
scl + [d.MethodDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.MethodOptions.DEPRECATED_FIELD_NUMBER],
852+
"This method has been marked as deprecated using proto method options.",
832853
)
854+
if both:
855+
wl("def {}(self) -> {}[{}, {}]:{}", method.name, self._import("typing", "Union"), type_str(method, is_async=False), type_str(method, is_async=True), " ..." if not self._has_comments(scl) else " ")
833856
else:
834-
wl("{}: {}{}", method.name, type_str(method, is_async=is_async), "" if not ignore_assignment_errors else " # type: ignore[assignment]")
835-
self._write_comments(scl)
857+
wl("def {}(self) -> {}:{}{}", method.name, type_str(method, is_async=is_async), " ..." if not self._has_comments(scl) else "", "" if not ignore_override_errors else " # type: ignore[override]")
858+
if self._has_comments(scl):
859+
with self._indent():
860+
if not self._write_comments(scl):
861+
wl("...")
836862

837863
def write_grpc_methods(self, service: d.ServiceDescriptorProto, scl_prefix: SourceCodeLocation) -> None:
838864
wl = self._write_line
@@ -924,7 +950,7 @@ def write_grpc_services(
924950
if self._write_comments(scl):
925951
wl("")
926952
wl("def __init__(self, channel: {}) -> None: ...", self._import("grpc.aio", "Channel"))
927-
self.write_grpc_stub_methods(service, scl, is_async=True, ignore_assignment_errors=True)
953+
self.write_grpc_stub_methods(service, scl, is_async=True, ignore_override_errors=True)
928954
wl("")
929955

930956
# The service definition interface

proto/testproto/grpc/dummy.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ service DeprecatedService {
3838
}
3939
// DeprecatedMethodNotDeprecatedRequest
4040
rpc DeprecatedMethodNotDeprecatedRequest(DummyRequest) returns (DummyReply) {
41+
// Method is deprecated, but request message is not
4142
option deprecated = true;
4243
}
4344
}

proto/testproto/test.proto

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ enum DeprecatedEnum {
193193
option deprecated = true;
194194
// Trailing comment
195195

196-
DEPRECATED_ONE = 1;
197-
DEPRECATED_TWO = 2;
196+
DEPRECATED_ONE = 1 [
197+
// This enum value is deprecated but this message doesn't show
198+
deprecated = true
199+
]; // Trailing comment for enum value
200+
DEPRECATED_TWO = 2 [deprecated = true];
201+
NOT_DEPRECATED = 3;
198202
}

stubtest_allowlist.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ testproto.nested.nested_pb2.AnotherNested.NestedMessage.NESTED_ENUM2
3636
testproto.nested.nested_pb2.AnotherNested.INVALID
3737
testproto.test_pb2.DEPRECATED_ONE
3838
testproto.test_pb2.DEPRECATED_TWO
39+
testproto.test_pb2.NOT_DEPRECATED
3940

4041
# Our enum types and helper types aren't there at runtime (Dynamic EnumTypeWrapper at runtime)
4142
# .*\..*EnumTypeWrapper$
@@ -246,9 +247,9 @@ testproto.test_pb2.Extensions1.ext
246247
.*_WhichOneofReturnType.*
247248
.*_WhichOneofArgType.*
248249

249-
#
250-
testproto.grpc.dummy_pb2_grpc.DeprecatedServiceStub.__init__
251-
testproto.grpc.dummy_pb2_grpc.DummyServiceStub.__init__
252-
testproto.grpc.dummy_pb2_grpc.ManyRPCsServiceStub.__init__
253-
testproto.grpc.import_pb2_grpc.SimpleServiceStub.__init__
250+
# Because we represent methods as properties, they do not exist at runtime
251+
testproto.grpc.dummy_pb2_grpc.DeprecatedServiceStub.*
252+
testproto.grpc.dummy_pb2_grpc.DummyServiceStub.*
253+
testproto.grpc.dummy_pb2_grpc.ManyRPCsServiceStub.*
254+
testproto.grpc.import_pb2_grpc.SimpleServiceStub.*
254255
testproto.grpc.dummy_pb2_grpc.EmptyServiceStub.__init__

0 commit comments

Comments
 (0)