diff --git a/CHANGELOG.md b/CHANGELOG.md index fb810841..a38d8958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### [1.10.1](https://www.github.com/googleapis/proto-plus-python/compare/v1.10.0...v1.10.1) (2020-10-08) + + +### Bug Fixes + +* accessing an unset struct_pb2.Value field does not raise ([#140](https://www.github.com/googleapis/proto-plus-python/issues/140)) ([d045cbf](https://www.github.com/googleapis/proto-plus-python/commit/d045cbf058cbb8f4ca98dd06741270fcaee865be)) +* add LICENSE and tests to package ([#146](https://www.github.com/googleapis/proto-plus-python/issues/146)) ([815c943](https://www.github.com/googleapis/proto-plus-python/commit/815c9439a1dadb2d4111784eb18ba673ce6e6cc2)) + ## [1.10.0](https://www.github.com/googleapis/proto-plus-python/compare/v1.9.1...v1.10.0) (2020-09-24) diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..e6c96660 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include README.rst LICENSE +recursive-include tests * +global-exclude *.py[co] +global-exclude __pycache__ \ No newline at end of file diff --git a/proto/marshal/rules/struct.py b/proto/marshal/rules/struct.py index 53ef34fa..27dfaf56 100644 --- a/proto/marshal/rules/struct.py +++ b/proto/marshal/rules/struct.py @@ -29,11 +29,15 @@ def __init__(self, *, marshal): def to_python(self, value, *, absent: bool = None): """Coerce the given value to the appropriate Python type. - Note that setting ``null_value`` is distinct from not setting - a value, and the absent value will raise an exception. + Note that both NullValue and absent fields return None. + In order to disambiguate between these two options, + use containment check, + E.g. + "value" in foo + which is True for NullValue and False for an absent value. """ kind = value.WhichOneof("kind") - if kind == "null_value": + if kind == "null_value" or absent: return None if kind == "bool_value": return bool(value.bool_value) @@ -49,7 +53,9 @@ def to_python(self, value, *, absent: bool = None): return self._marshal.to_python( struct_pb2.ListValue, value.list_value, absent=False, ) - raise AttributeError + # If more variants are ever added, we want to fail loudly + # instead of tacitly returning None. + raise ValueError("Unexpected kind: %s" % kind) # pragma: NO COVER def to_proto(self, value) -> struct_pb2.Value: """Return a protobuf Value object representing this value.""" diff --git a/setup.py b/setup.py index 4ce2e91e..695611a6 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ from setuptools import find_packages, setup -version = "1.10.0" +version = "1.10.1" PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) diff --git a/tests/conftest.py b/tests/conftest.py index 7e7265f7..b60b91d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import imp +import importlib from unittest import mock from google.protobuf import descriptor_pool @@ -76,11 +76,11 @@ def pytest_runtest_setup(item): # If the marshal had previously registered the old message classes, # then reload the appropriate modules so the marshal is using the new ones. if "wrappers_pb2" in reloaded: - imp.reload(rules.wrappers) + importlib.reload(rules.wrappers) if "struct_pb2" in reloaded: - imp.reload(rules.struct) + importlib.reload(rules.struct) if reloaded.intersection({"timestamp_pb2", "duration_pb2"}): - imp.reload(rules.dates) + importlib.reload(rules.dates) def pytest_runtest_teardown(item): diff --git a/tests/test_marshal_types_struct.py b/tests/test_marshal_types_struct.py index 82a40c22..914730c5 100644 --- a/tests/test_marshal_types_struct.py +++ b/tests/test_marshal_types_struct.py @@ -30,6 +30,13 @@ class Foo(proto.Message): assert Foo(value=True).value is True +def test_value_absent(): + class Foo(proto.Message): + value = proto.Field(struct_pb2.Value, number=1) + + assert Foo().value is None + + def test_value_primitives_rmw(): class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) @@ -158,7 +165,7 @@ class Foo(proto.Message): value = proto.Field(struct_pb2.Value, number=1) foo = Foo() - assert not hasattr(foo, "value") + assert "value" not in foo def test_list_value_read():