Skip to content

Commit 69f4192

Browse files
authored
Fix incorrect deprecation warnings on defaults (#348)
This change ensures that deprecation warnings are only raised when either a deprecated field is explicitly set or a deprecated message is initialised. Resolves: #347
1 parent 9c1bf25 commit 69f4192

File tree

7 files changed

+62
-36
lines changed

7 files changed

+62
-36
lines changed

src/betterproto/__init__.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import dataclasses
22
import enum
3-
import inspect
43
import json
54
import math
65
import struct
76
import sys
87
import typing
8+
import warnings
99
from abc import ABC
1010
from base64 import b64decode, b64encode
1111
from copy import deepcopy
1212
from datetime import datetime, timedelta, timezone
13-
from dateutil.parser import isoparse
1413
from typing import (
1514
Any,
1615
Callable,
@@ -26,12 +25,13 @@
2625
get_type_hints,
2726
)
2827

28+
from dateutil.parser import isoparse
29+
2930
from ._types import T
3031
from ._version import __version__
3132
from .casing import camel_case, safe_snake_case, snake_case
3233
from .grpc.grpclib_client import ServiceStub
3334

34-
3535
# Proto 3 data types
3636
TYPE_ENUM = "enum"
3737
TYPE_BOOL = "bool"
@@ -867,7 +867,10 @@ def _cls_for(cls, field: dataclasses.Field, index: int = 0) -> Type:
867867
return field_cls
868868

869869
def _get_field_default(self, field_name: str) -> Any:
870-
return self._betterproto.default_gen[field_name]()
870+
with warnings.catch_warnings():
871+
# ignore warnings when initialising deprecated field defaults
872+
warnings.filterwarnings("ignore", category=DeprecationWarning)
873+
return self._betterproto.default_gen[field_name]()
871874

872875
@classmethod
873876
def _get_field_default_gen(cls, field: dataclasses.Field) -> Any:
@@ -1288,6 +1291,22 @@ def from_json(self: T, value: Union[str, bytes]) -> T:
12881291
"""
12891292
return self.from_dict(json.loads(value))
12901293

1294+
def is_set(self, name: str) -> bool:
1295+
"""
1296+
Check if field with the given name has been set.
1297+
1298+
Parameters
1299+
-----------
1300+
name: :class:`str`
1301+
The name of the field to check for.
1302+
1303+
Returns
1304+
--------
1305+
:class:`bool`
1306+
`True` if field has been set, otherwise `False`.
1307+
"""
1308+
return self.__raw_get(name) is not PLACEHOLDER
1309+
12911310

12921311
def serialized_on_wire(message: Message) -> bool:
12931312
"""

src/betterproto/templates/template.py.j2

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class {{ message.py_name }}(betterproto.Message):
6363
{% endif %}
6464
super().__post_init__()
6565
{% for field in message.deprecated_fields %}
66-
if self.{{ field }}:
66+
if self.is_set("{{ field }}"):
6767
warnings.warn("{{ message.py_name }}.{{ field }} is deprecated", DeprecationWarning)
6868
{% endfor %}
6969
{% endif %}
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2-
"v": 10,
2+
"message": {
3+
"value": "hello"
4+
},
35
"value": 10
46
}

tests/inputs/deprecated/deprecated.proto

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ package deprecated;
44

55
// Some documentation about the Test message.
66
message Test {
7-
// Some documentation about the value.
8-
option deprecated = true;
9-
int32 v = 1 [deprecated=true];
7+
Message message = 1 [deprecated=true];
108
int32 value = 2;
119
}
10+
11+
message Message {
12+
option deprecated = true;
13+
string value = 1;
14+
}

tests/inputs/deprecated_field/deprecated_field.json

-4
This file was deleted.

tests/inputs/deprecated_field/deprecated_field.proto

-10
This file was deleted.

tests/test_deprecated.py

+29-13
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,42 @@
1+
import warnings
2+
13
import pytest
24

3-
from tests.output_betterproto.deprecated import Test as DeprecatedMessageTest
4-
from tests.output_betterproto.deprecated_field import Test as DeprecatedFieldTest
5+
from tests.output_betterproto.deprecated import Message, Test
6+
7+
8+
@pytest.fixture
9+
def message():
10+
with warnings.catch_warnings():
11+
warnings.filterwarnings("ignore", category=DeprecationWarning)
12+
return Message(value="hello")
513

614

715
def test_deprecated_message():
8-
with pytest.deprecated_call():
9-
DeprecatedMessageTest(value=10)
16+
with pytest.warns(DeprecationWarning) as record:
17+
Message(value="hello")
1018

19+
assert len(record) == 1
20+
assert str(record[0].message) == f"{Message.__name__} is deprecated"
1121

12-
def test_deprecated_message_with_deprecated_field():
13-
with pytest.warns(None) as record:
14-
DeprecatedMessageTest(v=10, value=10)
15-
assert len(record) == 2
1622

23+
def test_message_with_deprecated_field(message):
24+
with pytest.warns(DeprecationWarning) as record:
25+
Test(message=message, value=10)
26+
27+
assert len(record) == 1
28+
assert str(record[0].message) == f"{Test.__name__}.message is deprecated"
1729

18-
def test_deprecated_field_warning():
19-
with pytest.deprecated_call():
20-
DeprecatedFieldTest(v=10, value=10)
2130

31+
def test_message_with_deprecated_field_not_set(message):
32+
with pytest.warns(None) as record:
33+
Test(value=10)
34+
35+
assert not record
2236

23-
def test_deprecated_field_no_warning():
37+
38+
def test_message_with_deprecated_field_not_set_default(message):
2439
with pytest.warns(None) as record:
25-
DeprecatedFieldTest(value=10)
40+
_ = Test(value=10).message
41+
2642
assert not record

0 commit comments

Comments
 (0)