Skip to content

Commit d00e3bb

Browse files
committed
allow message field deprecation as well
Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
1 parent 3af9d03 commit d00e3bb

30 files changed

+458
-399
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
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
15+
- Export stub methods as properties instead of attributes if deprecated and mark as such
16+
- Export enum fields as properties on class level (not module level) enums if deprecated and mark as such
17+
- Export fields as properties with getters/setters if deprecated and mark as such
1718

1819
## 3.7.0
1920

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
@@ -361,25 +361,30 @@ def write_enum_values(
361361
value_type: str,
362362
scl_prefix: SourceCodeLocation,
363363
*,
364-
as_properties: bool = False,
364+
class_attributes: bool = False,
365365
) -> None:
366366
for i, val in values:
367367
if val.name in PYTHON_RESERVED:
368368
continue
369369

370370
scl = scl_prefix + [i]
371371
# Class level
372-
if as_properties:
373-
self._write_line("@property")
372+
if class_attributes:
374373
if val.options.deprecated:
374+
self._write_line("@property")
375375
self._write_deprecation_warning(
376376
scl + [d.EnumValueDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.EnumOptions.DEPRECATED_FIELD_NUMBER],
377377
"This enum value has been marked as deprecated using proto enum value options.",
378378
)
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():
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+
else:
385+
self._write_line(
386+
f"{val.name}: {value_type} # {val.number}",
387+
)
383388
self._write_comments(scl)
384389
# Module level
385390
else:
@@ -433,7 +438,7 @@ def write_enums(
433438
[(i, v) for i, v in enumerate(enum.value) if v.name not in PROTO_ENUM_RESERVED],
434439
value_type_helper_fq,
435440
scl + [d.EnumDescriptorProto.VALUE_FIELD_NUMBER],
436-
as_properties=True,
441+
class_attributes=True,
437442
)
438443
wl("")
439444

@@ -525,9 +530,34 @@ def write_messages(
525530
continue
526531
field_type = self.python_type(field)
527532
if is_scalar(field) and field.label != d.FieldDescriptorProto.LABEL_REPEATED:
528-
# Scalar non repeated fields are r/w
529-
wl(f"{field.name}: {field_type}")
530-
self._write_comments(scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx])
533+
# Scalar non repeated fields are r/w, generate getter and setter if deprecated
534+
scl_field = scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx]
535+
if field.options.deprecated:
536+
wl("@property")
537+
self._write_deprecation_warning(
538+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
539+
"This field has been marked as deprecated using proto field options.",
540+
)
541+
body = " ..." if not self._has_comments(scl_field) else ""
542+
wl(f"def {field.name}(self) -> {field_type}:{body}")
543+
if self._has_comments(scl_field):
544+
with self._indent():
545+
self._write_comments(scl_field)
546+
wl("")
547+
wl(f"@{field.name}.setter")
548+
self._write_deprecation_warning(
549+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
550+
"This field has been marked as deprecated using proto field options.",
551+
)
552+
body = " ..." if not self._has_comments(scl_field) else ""
553+
wl(f"def {field.name}(self, value: {field_type}) -> None:{body}")
554+
if self._has_comments(scl_field):
555+
with self._indent():
556+
self._write_comments(scl_field)
557+
wl("")
558+
else:
559+
wl(f"{field.name}: {field_type}")
560+
self._write_comments(scl_field)
531561

532562
for idx, field in enumerate(desc.field):
533563
if field.name in PYTHON_RESERVED:
@@ -537,6 +567,11 @@ def write_messages(
537567
# r/o Getters for non-scalar fields and scalar-repeated fields
538568
scl_field = scl + [d.DescriptorProto.FIELD_FIELD_NUMBER, idx]
539569
wl("@property")
570+
if field.options.deprecated:
571+
self._write_deprecation_warning(
572+
scl_field + [d.FieldDescriptorProto.OPTIONS_FIELD_NUMBER] + [d.FieldOptions.DEPRECATED_FIELD_NUMBER],
573+
"This field has been marked as deprecated using proto field options.",
574+
)
540575
body = " ..." if not self._has_comments(scl_field) else ""
541576
wl(f"def {field.name}(self) -> {field_type}:{body}")
542577
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
@@ -178,6 +178,7 @@ for PY_VER in $PY_VER_UNIT_TESTS; do
178178
if [[ "$PY_VER_MYPY" == "3.13.9" ]] || [[ "$PY_VER_MYPY" == "3.14.0" ]]; then
179179
echo "Skipping stubtest for Python $PY_VER_MYPY until positional argument decision is made"
180180
else
181+
echo "Running stubtest for Python $PY_VER_MYPY with API implementation: $API_IMPL"
181182
PYTHONPATH=test/generated python3 -m mypy.stubtest ${CUSTOM_TYPESHED_DIR_ARG:+"$CUSTOM_TYPESHED_DIR_ARG"} --allowlist stubtest_allowlist.txt testproto
182183
fi
183184
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)