From 0cd9510b545807dab93b35f858620383a63dc9ee Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 30 Jul 2020 14:47:01 +0200 Subject: [PATCH] Support deprecated message and fields (#126) --- src/betterproto/plugin/models.py | 16 ++++++++++++ src/betterproto/templates/template.py.j2 | 15 +++++++++++ tests/inputs/deprecated/deprecated.json | 4 +++ tests/inputs/deprecated/deprecated.proto | 9 +++++++ .../deprecated_field/deprecated_field.json | 4 +++ .../deprecated_field/deprecated_field.proto | 8 ++++++ tests/test_deprecated.py | 26 +++++++++++++++++++ 7 files changed, 82 insertions(+) create mode 100644 tests/inputs/deprecated/deprecated.json create mode 100644 tests/inputs/deprecated/deprecated.proto create mode 100644 tests/inputs/deprecated_field/deprecated_field.json create mode 100644 tests/inputs/deprecated_field/deprecated_field.proto create mode 100644 tests/test_deprecated.py diff --git a/src/betterproto/plugin/models.py b/src/betterproto/plugin/models.py index 8e19961..48b73bb 100644 --- a/src/betterproto/plugin/models.py +++ b/src/betterproto/plugin/models.py @@ -33,6 +33,7 @@ import re from dataclasses import dataclass from dataclasses import field from typing import ( + Iterator, Union, Type, List, @@ -249,6 +250,13 @@ class OutputTemplate: """ return [f.name for f in self.input_files] + @property + def python_module_imports(self) -> Set[str]: + imports = set() + if any(x for x in self.messages if any(x.deprecated_fields)): + imports.add("warnings") + return imports + @dataclass class MessageCompiler(ProtoContentBase): @@ -261,6 +269,7 @@ class MessageCompiler(ProtoContentBase): fields: List[Union["FieldCompiler", "MessageCompiler"]] = field( default_factory=list ) + deprecated: bool = field(default=False, init=False) def __post_init__(self): # Add message to output file @@ -269,6 +278,7 @@ class MessageCompiler(ProtoContentBase): self.output_file.enums.append(self) else: self.output_file.messages.append(self) + self.deprecated = self.proto_obj.options.deprecated super().__post_init__() @property @@ -285,6 +295,12 @@ class MessageCompiler(ProtoContentBase): return f"List[{self.py_name}]" return self.py_name + @property + def deprecated_fields(self) -> Iterator[str]: + for f in self.fields: + if f.deprecated: + yield f.py_name + def is_map( proto_field_obj: FieldDescriptorProto, parent_message: DescriptorProto diff --git a/src/betterproto/templates/template.py.j2 b/src/betterproto/templates/template.py.j2 index bbd7cc5..fa260e4 100644 --- a/src/betterproto/templates/template.py.j2 +++ b/src/betterproto/templates/template.py.j2 @@ -1,6 +1,9 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: {{ ', '.join(description.input_filenames) }} # plugin: python-betterproto +{% for i in description.python_module_imports|sort %} +import {{ i }} +{% endfor %} from dataclasses import dataclass {% if description.datetime_imports %} from datetime import {% for i in description.datetime_imports|sort %}{{ i }}{% if not loop.last %}, {% endif %}{% endfor %} @@ -50,6 +53,18 @@ class {{ message.py_name }}(betterproto.Message): pass {% endif %} + {% if message.deprecated or message.deprecated_fields %} + def __post_init__(self) -> None: + {% if message.deprecated %} + warnings.warn("{{ message.py_name }} is deprecated", DeprecationWarning) + {% endif %} + super().__post_init__() + {% for field in message.deprecated_fields %} + if self.{{ field }}: + warnings.warn("{{ message.py_name }}.{{ field }} is deprecated", DeprecationWarning) + {% endfor %} + {% endif %} + {% endfor %} {% for service in description.services %} diff --git a/tests/inputs/deprecated/deprecated.json b/tests/inputs/deprecated/deprecated.json new file mode 100644 index 0000000..9da52bb --- /dev/null +++ b/tests/inputs/deprecated/deprecated.json @@ -0,0 +1,4 @@ +{ + "v": 10, + "value": 10 +} diff --git a/tests/inputs/deprecated/deprecated.proto b/tests/inputs/deprecated/deprecated.proto new file mode 100644 index 0000000..aa1f818 --- /dev/null +++ b/tests/inputs/deprecated/deprecated.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +// Some documentation about the Test message. +message Test { + // Some documentation about the value. + option deprecated = true; + int32 v = 1 [deprecated=true]; + int32 value = 2; +} diff --git a/tests/inputs/deprecated_field/deprecated_field.json b/tests/inputs/deprecated_field/deprecated_field.json new file mode 100644 index 0000000..9da52bb --- /dev/null +++ b/tests/inputs/deprecated_field/deprecated_field.json @@ -0,0 +1,4 @@ +{ + "v": 10, + "value": 10 +} diff --git a/tests/inputs/deprecated_field/deprecated_field.proto b/tests/inputs/deprecated_field/deprecated_field.proto new file mode 100644 index 0000000..04de1a9 --- /dev/null +++ b/tests/inputs/deprecated_field/deprecated_field.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +// Some documentation about the Test message. +message Test { + // Some documentation about the value. + int32 v = 1 [deprecated=true]; + int32 value = 2; +} diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py new file mode 100644 index 0000000..cbdea33 --- /dev/null +++ b/tests/test_deprecated.py @@ -0,0 +1,26 @@ +import pytest + +from tests.output_betterproto.deprecated import Test as DeprecatedMessageTest +from tests.output_betterproto.deprecated_field import Test as DeprecatedFieldTest + + +def test_deprecated_message(): + with pytest.deprecated_call(): + DeprecatedMessageTest(value=10) + + +def test_deprecated_message_with_deprecated_field(): + with pytest.warns(None) as record: + DeprecatedMessageTest(v=10, value=10) + assert len(record) == 2 + + +def test_deprecated_field_warning(): + with pytest.deprecated_call(): + DeprecatedFieldTest(v=10, value=10) + + +def test_deprecated_field_no_warning(): + with pytest.warns(None) as record: + DeprecatedFieldTest(value=10) + assert not record