Skip to content

Commit 734aaea

Browse files
committed
allow message field deprecation as well
Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
1 parent 99bc340 commit 734aaea

30 files changed

+458
-399
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
- Add `_WhichOneofArgType_<oneof_name>` and `_WhichOneofReturnType_<oneof_name>` type aliases
1414
- Use `__new__` overloads for async stubs instead of `TypeVar` based `__init__` overloads.
1515
- https://github.com/nipunn1313/mypy-protobuf/issues/707
16-
- Export stub methods as properties instead of attributes. This allows for deprecation
17-
- Export enum fields as properties on class level (not module level) enums. This allows for deprecation
16+
- Export stub methods as properties instead of attributes if deprecated and mark as such
17+
- Export enum fields as properties on class level (not module level) enums if deprecated and mark as such
18+
- Export fields as properties with getters/setters if deprecated and mark as such
1819

1920
## 3.7.0
2021

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ See [Changelog](CHANGELOG.md) for full listing
103103
* There are differences in how `mypy-protobuf` and `pyi_out` generate enums. See [this issue](https://github.com/protocolbuffers/protobuf/issues/8175) for details
104104
* Type aliases exported for `HasField`, `WhichOneof` and `ClearField` arguments
105105
* Parses comments as docstrings
106+
* `mypy-protobuf` marks enums, enum values, messages, message fields, services, and methods with `@warnings.deprecated` if the deprecation option is set to true.
106107

107108
#### Examples
108109

mypy_protobuf/main.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -392,25 +392,30 @@ def write_enum_values(
392392
value_type: str,
393393
scl_prefix: SourceCodeLocation,
394394
*,
395-
as_properties: bool = False,
395+
class_attributes: bool = False,
396396
) -> None:
397397
for i, val in values:
398398
if val.name in PYTHON_RESERVED:
399399
continue
400400

401401
scl = scl_prefix + [i]
402402
# Class level
403-
if as_properties:
404-
self._write_line("@property")
403+
if class_attributes:
405404
if val.options.deprecated:
405+
self._write_line("@property")
406406
self._write_deprecation_warning(
407407
scl + [d.EnumValueDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.EnumOptions.DEPRECATED_FIELD_NUMBER],
408408
"This enum value has been marked as deprecated using proto enum value options.",
409409
)
410-
self._write_line(
411-
f"def {val.name}(self) -> {value_type}: {'' if self._has_comments(scl) else '...'} # {val.number}",
412-
)
413-
with self._indent():
410+
self._write_line(
411+
f"def {val.name}(self) -> {value_type}: {'' if self._has_comments(scl) else '...'} # {val.number}",
412+
)
413+
with self._indent():
414+
self._write_comments(scl)
415+
else:
416+
self._write_line(
417+
f"{val.name}: {value_type} # {val.number}",
418+
)
414419
self._write_comments(scl)
415420
# Module level
416421
else:
@@ -464,7 +469,7 @@ def write_enums(
464469
[(i, v) for i, v in enumerate(enum_proto.value) if v.name not in PROTO_ENUM_RESERVED],
465470
value_type_helper_fq,
466471
scl + [d.EnumDescriptorProto.VALUE_FIELD_NUMBER],
467-
as_properties=True,
472+
class_attributes=True,
468473
)
469474
wl("")
470475

@@ -556,9 +561,34 @@ def write_messages(
556561
continue
557562
field_type = self.python_type(field)
558563
if is_scalar(field) and field.label != d.FieldDescriptorProto.LABEL_REPEATED:
559-
# Scalar non repeated fields are r/w
560-
wl(f"{field.name}: {field_type}")
561-
self._write_comments(scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx])
564+
# Scalar non repeated fields are r/w, generate getter and setter if deprecated
565+
scl_field = scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx]
566+
if field.options.deprecated:
567+
wl("@property")
568+
self._write_deprecation_warning(
569+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
570+
"This field has been marked as deprecated using proto field options.",
571+
)
572+
body = " ..." if not self._has_comments(scl_field) else ""
573+
wl(f"def {field.name}(self) -> {field_type}:{body}")
574+
if self._has_comments(scl_field):
575+
with self._indent():
576+
self._write_comments(scl_field)
577+
wl("")
578+
wl(f"@{field.name}.setter")
579+
self._write_deprecation_warning(
580+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
581+
"This field has been marked as deprecated using proto field options.",
582+
)
583+
body = " ..." if not self._has_comments(scl_field) else ""
584+
wl(f"def {field.name}(self, value: {field_type}) -> None:{body}")
585+
if self._has_comments(scl_field):
586+
with self._indent():
587+
self._write_comments(scl_field)
588+
wl("")
589+
else:
590+
wl(f"{field.name}: {field_type}")
591+
self._write_comments(scl_field)
562592

563593
for idx, field in enumerate(desc.field):
564594
if field.name in PYTHON_RESERVED:
@@ -568,6 +598,11 @@ def write_messages(
568598
# r/o Getters for non-scalar fields and scalar-repeated fields
569599
scl_field = scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx]
570600
wl("@property")
601+
if field.options.deprecated:
602+
self._write_deprecation_warning(
603+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
604+
"This field has been marked as deprecated using proto field options.",
605+
)
571606
body = " ..." if not self._has_comments(scl_field) else ""
572607
wl(f"def {field.name}(self) -> {field_type}:{body}")
573608
if self._has_comments(scl_field):

proto/testproto/test.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ message DeprecatedMessage {
175175
option deprecated = true;
176176

177177
optional string a_string = 1;
178+
optional string deprecated_field = 2 [deprecated = true];
178179
}
179180

180181
message DeprecatedMessageBadComment {

run_test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ for PY_VER in $PY_VER_UNIT_TESTS; do
197197
if [[ "$PY_VER_MYPY" == "3.13.9" ]] || [[ "$PY_VER_MYPY" == "3.14.0" ]]; then
198198
echo "Skipping stubtest for Python $PY_VER_MYPY until positional argument decision is made"
199199
else
200+
echo "Running stubtest for Python $PY_VER_MYPY with API implementation: $API_IMPL"
200201
PYTHONPATH=test/generated python3 -m mypy.stubtest ${CUSTOM_TYPESHED_DIR_ARG:+"$CUSTOM_TYPESHED_DIR_ARG"} --allowlist stubtest_allowlist.txt testproto
201202
fi
202203
fi

stubtest_allowlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,7 @@ testproto.grpc.dummy_pb2_grpc.DummyServiceStub.*
253253
testproto.grpc.dummy_pb2_grpc.ManyRPCsServiceStub.*
254254
testproto.grpc.import_pb2_grpc.SimpleServiceStub.*
255255
testproto.grpc.dummy_pb2_grpc.EmptyServiceStub.__init__
256+
257+
# Because we generate message fields as properties when deprecated they do not exist at runtime
258+
testproto.grpc.dummy_pb2.DeprecatedRequest.old_field
259+
testproto.test_pb2.DeprecatedMessage.deprecated_field

test/generated/testproto/grpc/dummy_pb2.pyi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ class DeprecatedRequest(google.protobuf.message.Message):
5959
DESCRIPTOR: google.protobuf.descriptor.Descriptor
6060

6161
OLD_FIELD_FIELD_NUMBER: builtins.int
62-
old_field: builtins.str
62+
@property
63+
@deprecated("""This field has been marked as deprecated using proto field options.""")
64+
def old_field(self) -> builtins.str: ...
65+
@old_field.setter
66+
@deprecated("""This field has been marked as deprecated using proto field options.""")
67+
def old_field(self, value: builtins.str) -> None: ...
6368
def __init__(
6469
self,
6570
*,

test/generated/testproto/nested/nested_pb2.pyi

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,9 @@ class AnotherNested(google.protobuf.message.Message):
4444

4545
class _NestedEnumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[AnotherNested._NestedEnum.ValueType], builtins.type):
4646
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
47-
@property
48-
def INVALID(self) -> AnotherNested._NestedEnum.ValueType: ... # 0
49-
@property
50-
def ONE(self) -> AnotherNested._NestedEnum.ValueType: ... # 1
51-
@property
52-
def TWO(self) -> AnotherNested._NestedEnum.ValueType: ... # 2
47+
INVALID: AnotherNested._NestedEnum.ValueType # 0
48+
ONE: AnotherNested._NestedEnum.ValueType # 1
49+
TWO: AnotherNested._NestedEnum.ValueType # 2
5350

5451
class NestedEnum(_NestedEnum, metaclass=_NestedEnumEnumTypeWrapper): ...
5552
INVALID: AnotherNested.NestedEnum.ValueType # 0
@@ -66,12 +63,9 @@ class AnotherNested(google.protobuf.message.Message):
6663

6764
class _NestedEnum2EnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[AnotherNested.NestedMessage._NestedEnum2.ValueType], builtins.type):
6865
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
69-
@property
70-
def UNDEFINED(self) -> AnotherNested.NestedMessage._NestedEnum2.ValueType: ... # 0
71-
@property
72-
def NESTED_ENUM1(self) -> AnotherNested.NestedMessage._NestedEnum2.ValueType: ... # 1
73-
@property
74-
def NESTED_ENUM2(self) -> AnotherNested.NestedMessage._NestedEnum2.ValueType: ... # 2
66+
UNDEFINED: AnotherNested.NestedMessage._NestedEnum2.ValueType # 0
67+
NESTED_ENUM1: AnotherNested.NestedMessage._NestedEnum2.ValueType # 1
68+
NESTED_ENUM2: AnotherNested.NestedMessage._NestedEnum2.ValueType # 2
7569

7670
class NestedEnum2(_NestedEnum2, metaclass=_NestedEnum2EnumTypeWrapper): ...
7771
UNDEFINED: AnotherNested.NestedMessage.NestedEnum2.ValueType # 0

test/generated/testproto/readme_enum_pb2.pyi

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ class _MyEnum:
2222

2323
class _MyEnumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_MyEnum.ValueType], builtins.type):
2424
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
25-
@property
26-
def HELLO(self) -> _MyEnum.ValueType: ... # 0
27-
@property
28-
def WORLD(self) -> _MyEnum.ValueType: ... # 1
25+
HELLO: _MyEnum.ValueType # 0
26+
WORLD: _MyEnum.ValueType # 1
2927

3028
class MyEnum(_MyEnum, metaclass=_MyEnumEnumTypeWrapper): ...
3129

test/generated/testproto/test3_pb2.pyi

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@ class _OuterEnum:
2626

2727
class _OuterEnumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_OuterEnum.ValueType], builtins.type):
2828
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
29-
@property
30-
def UNKNOWN(self) -> _OuterEnum.ValueType: ... # 0
31-
@property
32-
def FOO3(self) -> _OuterEnum.ValueType: ... # 1
33-
@property
34-
def BAR3(self) -> _OuterEnum.ValueType: ... # 2
29+
UNKNOWN: _OuterEnum.ValueType # 0
30+
FOO3: _OuterEnum.ValueType # 1
31+
BAR3: _OuterEnum.ValueType # 2
3532

3633
class OuterEnum(_OuterEnum, metaclass=_OuterEnumEnumTypeWrapper): ...
3734

@@ -66,10 +63,8 @@ class SimpleProto3(google.protobuf.message.Message):
6663

6764
class _InnerEnumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SimpleProto3._InnerEnum.ValueType], builtins.type):
6865
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
69-
@property
70-
def INNER1(self) -> SimpleProto3._InnerEnum.ValueType: ... # 0
71-
@property
72-
def INNER2(self) -> SimpleProto3._InnerEnum.ValueType: ... # 1
66+
INNER1: SimpleProto3._InnerEnum.ValueType # 0
67+
INNER2: SimpleProto3._InnerEnum.ValueType # 1
7368

7469
class InnerEnum(_InnerEnum, metaclass=_InnerEnumEnumTypeWrapper): ...
7570
INNER1: SimpleProto3.InnerEnum.ValueType # 0

0 commit comments

Comments
 (0)