Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-proto-plus for
openSUSE:Factory checked in at 2026-07-29 19:01:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-proto-plus (Old)
and /work/SRC/openSUSE:Factory/.python-proto-plus.new.2004 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-proto-plus"
Wed Jul 29 19:01:23 2026 rev:21 rq:1368357 version:1.28.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-proto-plus/python-proto-plus.changes
2026-07-06 12:34:17.251468073 +0200
+++
/work/SRC/openSUSE:Factory/.python-proto-plus.new.2004/python-proto-plus.changes
2026-07-29 19:02:51.657116974 +0200
@@ -1,0 +2,10 @@
+Wed Jul 29 11:06:00 UTC 2026 - Matthias Fehring <[email protected]> -
1.28.2
+
+- update to 1.28.2
+ * make Marshal thread-safe and handle race conditions
+ (gh#googleapis/google-cloud-python#15100)
+- changes from 1.28.1
+ * add --cov-append to gapic-generator and proto-plus to preserve
+ monorepo coverage
+
+-------------------------------------------------------------------
Old:
----
proto_plus-1.28.0.tar.gz
New:
----
proto_plus-1.28.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-proto-plus.spec ++++++
--- /var/tmp/diff_new_pack.u9ZWpr/_old 2026-07-29 19:02:52.177134896 +0200
+++ /var/tmp/diff_new_pack.u9ZWpr/_new 2026-07-29 19:02:52.181135033 +0200
@@ -27,7 +27,7 @@
%define modname proto_plus
%{?sle15_python_module_pythons}
Name: python-proto-plus%{psuffix}
-Version: 1.28.0
+Version: 1.28.2
Release: 0
Summary: Pythonic Protocol Buffers
License: Apache-2.0
++++++ proto_plus-1.28.0.tar.gz -> proto_plus-1.28.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/proto_plus-1.28.0/PKG-INFO
new/proto_plus-1.28.2/PKG-INFO
--- old/proto_plus-1.28.0/PKG-INFO 2026-05-07 09:16:58.869743600 +0200
+++ new/proto_plus-1.28.2/PKG-INFO 2026-07-22 18:00:40.291706800 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: proto-plus
-Version: 1.28.0
+Version: 1.28.2
Summary: Beautiful, Pythonic protocol buffers
Author-email: Google LLC <[email protected]>
License: Apache 2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/proto_plus-1.28.0/proto/marshal/marshal.py
new/proto_plus-1.28.2/proto/marshal/marshal.py
--- old/proto_plus-1.28.0/proto/marshal/marshal.py 2026-05-07
09:09:08.000000000 +0200
+++ new/proto_plus-1.28.2/proto/marshal/marshal.py 2026-07-22
17:58:56.000000000 +0200
@@ -13,24 +13,20 @@
# limitations under the License.
import abc
+import threading
-from google.protobuf import duration_pb2
-from google.protobuf import timestamp_pb2
-from google.protobuf import field_mask_pb2
-from google.protobuf import struct_pb2
-from google.protobuf import wrappers_pb2
+from google.protobuf import (
+ duration_pb2,
+ field_mask_pb2,
+ struct_pb2,
+ timestamp_pb2,
+ wrappers_pb2,
+)
from proto.marshal import compat
-from proto.marshal.collections import MapComposite
-from proto.marshal.collections import Repeated
-from proto.marshal.collections import RepeatedComposite
-
+from proto.marshal.collections import MapComposite, Repeated, RepeatedComposite
from proto.marshal.rules import bytes as pb_bytes
-from proto.marshal.rules import stringy_numbers
-from proto.marshal.rules import dates
-from proto.marshal.rules import struct
-from proto.marshal.rules import wrappers
-from proto.marshal.rules import field_mask
+from proto.marshal.rules import dates, field_mask, stringy_numbers, struct,
wrappers
from proto.primitives import ProtoType
@@ -168,7 +164,10 @@
# See https://github.com/googleapis/proto-plus-python/issues/349
if rule == self._noop and hasattr(self, "_instances"):
for _, instance in self._instances.items():
- rule = instance._rules.get(proto_type, self._noop)
+ # Avoid race condition where instance is added to _instances
+ # but __init__ hasn't run yet.
+ rules = getattr(instance, "_rules", {})
+ rule = rules.get(proto_type, self._noop)
if rule != self._noop:
break
return rule
@@ -254,6 +253,7 @@
"""
_instances = {}
+ _instance_creation_lock = threading.Lock()
def __new__(cls, *, name: str):
"""Create a marshal instance.
@@ -265,7 +265,18 @@
"""
klass = cls._instances.get(name)
if klass is None:
- klass = cls._instances[name] = super().__new__(cls)
+ with cls._instance_creation_lock:
+ # Double check inside lock to confirm another thread hasn't
+ # created the instance while we were waiting for the lock.
+ klass = cls._instances.get(name)
+ if klass is None:
+ # Use Copy-on-Write to avoid 'RuntimeError: dictionary
changed size during iteration'
+ # in BaseMarshal.get_rule. This allows other threads to
iterate over the old
+ # dictionary safely while we replace it with a new one
atomically.
+ new_instances = cls._instances.copy()
+ klass = super().__new__(cls)
+ new_instances[name] = klass
+ cls._instances = new_instances
return klass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/proto_plus-1.28.0/proto/version.py
new/proto_plus-1.28.2/proto/version.py
--- old/proto_plus-1.28.0/proto/version.py 2026-05-07 09:09:08.000000000
+0200
+++ new/proto_plus-1.28.2/proto/version.py 2026-07-22 17:58:58.000000000
+0200
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-__version__ = "1.28.0"
+__version__ = "1.28.2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/proto_plus-1.28.0/proto_plus.egg-info/PKG-INFO
new/proto_plus-1.28.2/proto_plus.egg-info/PKG-INFO
--- old/proto_plus-1.28.0/proto_plus.egg-info/PKG-INFO 2026-05-07
09:16:58.000000000 +0200
+++ new/proto_plus-1.28.2/proto_plus.egg-info/PKG-INFO 2026-07-22
18:00:40.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: proto-plus
-Version: 1.28.0
+Version: 1.28.2
Summary: Beautiful, Pythonic protocol buffers
Author-email: Google LLC <[email protected]>
License: Apache 2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/proto_plus-1.28.0/proto_plus.egg-info/SOURCES.txt
new/proto_plus-1.28.2/proto_plus.egg-info/SOURCES.txt
--- old/proto_plus-1.28.0/proto_plus.egg-info/SOURCES.txt 2026-05-07
09:16:58.000000000 +0200
+++ new/proto_plus-1.28.2/proto_plus.egg-info/SOURCES.txt 2026-07-22
18:00:40.000000000 +0200
@@ -60,6 +60,7 @@
tests/test_marshal_register.py
tests/test_marshal_strict.py
tests/test_marshal_stringy_numbers.py
+tests/test_marshal_thread_safety.py
tests/test_marshal_types_dates.py
tests/test_marshal_types_enum.py
tests/test_marshal_types_message.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/proto_plus-1.28.0/tests/test_marshal_thread_safety.py
new/proto_plus-1.28.2/tests/test_marshal_thread_safety.py
--- old/proto_plus-1.28.0/tests/test_marshal_thread_safety.py 1970-01-01
01:00:00.000000000 +0100
+++ new/proto_plus-1.28.2/tests/test_marshal_thread_safety.py 2026-07-22
17:58:58.000000000 +0200
@@ -0,0 +1,58 @@
+from unittest.mock import patch
+
+from proto.marshal.marshal import Marshal
+
+
+def test_marshal_identity():
+ m1 = Marshal(name="foo")
+ m2 = Marshal(name="foo")
+ assert m1 is m2
+
+
+def test_marshal_different_names():
+ m1 = Marshal(name="foo")
+ m2 = Marshal(name="bar")
+ assert m1 is not m2
+
+
+def test_marshal_new_race_condition():
+ # Test the case where klass is None at line 266,
+ # but NOT None at line 271 (another thread created it).
+
+ from unittest.mock import MagicMock
+
+ mock_instances = MagicMock()
+
+ call_count = 0
+
+ def get_side_effect(name, default=None):
+ nonlocal call_count
+ call_count += 1
+ if call_count == 1:
+ return None # First check returns None
+ # Simulate another thread having created it
+ return "fake_instance"
+
+ mock_instances.get.side_effect = get_side_effect
+
+ with patch.object(Marshal, "_instances", mock_instances):
+ instance = Marshal(name="race_test")
+ assert instance == "fake_instance"
+
+
+def test_get_rule_uninitialized_instance():
+ class FakeMarshal:
+ # No _rules attribute
+ pass
+
+ m = Marshal(name="default")
+
+ # Inject FakeMarshal into Marshal._instances safely using patch.dict
+ with patch.dict(Marshal._instances, {"fake_uninitialized": FakeMarshal()}):
+
+ class DummyType:
+ pass
+
+ # This should not raise AttributeError because of getattr safety
+ rule = m.get_rule(DummyType)
+ assert rule == m._noop