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

Reply via email to