Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-param for openSUSE:Factory 
checked in at 2026-04-02 17:41:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-param (Old)
 and      /work/SRC/openSUSE:Factory/.python-param.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-param"

Thu Apr  2 17:41:22 2026 rev:36 rq:1344237 version:2.3.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-param/python-param.changes        
2026-03-13 21:22:00.358216241 +0100
+++ /work/SRC/openSUSE:Factory/.python-param.new.21863/python-param.changes     
2026-04-02 17:42:35.518889777 +0200
@@ -1,0 +2,6 @@
+Wed Apr  1 23:45:38 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.3.3:
+  * Ensure shared rx inputs are reused (#1117)
+
+-------------------------------------------------------------------

Old:
----
  param-2.3.2.tar.gz

New:
----
  param-2.3.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-param.spec ++++++
--- /var/tmp/diff_new_pack.I4Qxu8/_old  2026-04-02 17:42:36.190917532 +0200
+++ /var/tmp/diff_new_pack.I4Qxu8/_new  2026-04-02 17:42:36.194917697 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-param
-Version:        2.3.2
+Version:        2.3.3
 Release:        0
 Summary:        Declarative Python programming using Parameters
 License:        BSD-3-Clause

++++++ param-2.3.2.tar.gz -> param-2.3.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/param-2.3.2/PKG-INFO new/param-2.3.3/PKG-INFO
--- old/param-2.3.2/PKG-INFO    2020-02-02 01:00:00.000000000 +0100
+++ new/param-2.3.3/PKG-INFO    2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: param
-Version: 2.3.2
+Version: 2.3.3
 Summary: Declarative parameters for robust Python classes and a rich API for 
reactive programming
 Project-URL: Homepage, https://param.holoviz.org/
 Project-URL: Tracker, https://github.com/holoviz/param/issues
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/param-2.3.2/param/_version.py 
new/param-2.3.3/param/_version.py
--- old/param-2.3.2/param/_version.py   2020-02-02 01:00:00.000000000 +0100
+++ new/param-2.3.3/param/_version.py   2020-02-02 01:00:00.000000000 +0100
@@ -1,5 +1,6 @@
-# file generated by setuptools-scm
+# file generated by vcs-versioning
 # don't change, don't track in version control
+from __future__ import annotations
 
 __all__ = [
     "__version__",
@@ -10,25 +11,14 @@
     "commit_id",
 ]
 
-TYPE_CHECKING = False
-if TYPE_CHECKING:
-    from typing import Tuple
-    from typing import Union
-
-    VERSION_TUPLE = Tuple[Union[int, str], ...]
-    COMMIT_ID = Union[str, None]
-else:
-    VERSION_TUPLE = object
-    COMMIT_ID = object
-
 version: str
 __version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
-commit_id: COMMIT_ID
-__commit_id__: COMMIT_ID
+__version_tuple__: tuple[int | str, ...]
+version_tuple: tuple[int | str, ...]
+commit_id: str | None
+__commit_id__: str | None
 
-__version__ = version = '2.3.2'
-__version_tuple__ = version_tuple = (2, 3, 2)
+__version__ = version = '2.3.3'
+__version_tuple__ = version_tuple = (2, 3, 3)
 
 __commit_id__ = commit_id = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/param-2.3.2/param/reactive.py 
new/param-2.3.3/param/reactive.py
--- old/param-2.3.2/param/reactive.py   2020-02-02 01:00:00.000000000 +0100
+++ new/param-2.3.3/param/reactive.py   2020-02-02 01:00:00.000000000 +0100
@@ -1436,7 +1436,7 @@
 
     def __init__(
         self, obj=None, operation=None, fn=None, depth=0, method=None, 
prev=None,
-        _shared_obj=None, _current=None, _wrapper=None, **kwargs
+        _shared_obj=None, _current=None, _wrapper=None, _shared=None,  **kwargs
     ):
         # _init is used to prevent to __getattribute__ to execute its
         # specialized code.
@@ -1458,6 +1458,11 @@
         self._current_task = None
         self._error_state = None
         self._current_ = _current
+        # _shared is used for branching rx pipelines where we clone the input.
+        # Here we store the original shared input, which makes it possible to
+        # cache the input value as long as the shared instance does not store
+        # a diverging _method accessor.
+        self._shared = _shared
         if isinstance(obj, rx) and not prev:
             self._prev = obj
         else:
@@ -1529,6 +1534,17 @@
             root._dirty_obj = False
         return self._shared_obj[0]
 
+    @property
+    def _is_async(self) -> bool:
+        if not self._operation:
+            return False
+        fn = self._operation["fn"]
+        return (
+            inspect.iscoroutinefunction(fn) or
+            inspect.isasyncgenfunction(fn) or
+            inspect.isgeneratorfunction(fn)
+        )
+
     @_obj.setter
     def _obj(self, obj):
         if self._shared_obj is None:
@@ -1632,10 +1648,15 @@
         self._root._dirty_obj = True
         self._error_state = None
 
-    async def _resolve_async(self, obj):
+    async def _resolve_async(self, obj = None):
         import asyncio
         self._current_task = task = asyncio.current_task()
-        if inspect.isasyncgen(obj):
+        if obj is None:
+            if self._shared._current_task:
+                await self._shared._current_task
+            self._current_ = self._shared.rx.value
+            self._trigger.param.trigger('value')
+        elif inspect.isasyncgen(obj):
             async for val in obj:
                 if self._current_task is not task:
                     break
@@ -1647,7 +1668,7 @@
                 self._current_ = value
                 self._trigger.param.trigger('value')
 
-    def _lazy_resolve(self, obj):
+    def _lazy_resolve(self, obj = None):
         from .parameterized import async_executor
         if inspect.isgenerator(obj):
             obj = _to_async_gen(obj)
@@ -1662,10 +1683,24 @@
                 if obj is Skip or obj is Undefined:
                     self._current_ = Undefined
                     raise Skip
+                elif (
+                    self._shared is not None and
+                    self._method is None and
+                    self._shared._method is None
+                ):
+                    # If this rx is cloned from an shared input then we make 
use
+                    # of the shared.rx.value to ensure branching pipelines do
+                    # not have to recompute the inputs multiple times.
+                    if self._is_async:
+                        self._shared.rx.value # trigger async resolve
+                        self._lazy_resolve()
+                    else:
+                        self._current_ = self._shared.rx.value
+                    raise Skip
                 operation = self._operation
                 if operation:
                     obj = self._eval_operation(obj, operation)
-                    if inspect.isasyncgen(obj) or inspect.iscoroutine(obj) or 
inspect.isgenerator(obj):
+                    if self._is_async:
                         self._lazy_resolve(obj)
                         obj = Skip
                     if obj is Skip:
@@ -1723,7 +1758,7 @@
         if copy:
             kwargs = dict(
                 self._kwargs, _current=self._current, method=self._method,
-                prev=self._prev, **kwargs
+                prev=self._prev, _shared=self, **kwargs
             )
         else:
             kwargs = dict(prev=self, **dict(self._kwargs, **kwargs))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/param-2.3.2/tests/testreactive.py 
new/param-2.3.3/tests/testreactive.py
--- old/param-2.3.2/tests/testreactive.py       2020-02-02 01:00:00.000000000 
+0100
+++ new/param-2.3.3/tests/testreactive.py       2020-02-02 01:00:00.000000000 
+0100
@@ -775,3 +775,69 @@
     x = rx(1)
     with pytest.raises(AttributeError, match="'rx' has no attribute 'value'"):
         x.value = 1
+
+def test_shared_rx_only_triggers_once():
+    call_count = 0
+
+    class Model(param.Parameterized):
+        a = param.Number(1.0)
+
+    model = Model()
+
+    def expensive_compute(a):
+        nonlocal call_count
+        call_count += 1
+        return {"x": a + 1, "y": a * 2}
+
+    shared = rx(expensive_compute)(model.param.a)
+
+    x_rx = shared.rx.pipe(lambda d: d["x"])
+    y_rx = shared.rx.pipe(lambda d: d["y"])
+
+    x_rx.rx.value
+    y_rx.rx.value
+
+    assert call_count == 1
+
+    model.a = 2.0
+
+    x_rx.rx.value
+    y_rx.rx.value
+
+    assert call_count == 2
+
+
+async def test_async_shared_rx_only_triggers_once():
+    call_count = 0
+
+    class Model(param.Parameterized):
+        a = param.Number(1.0)
+
+    model = Model()
+
+    async def expensive_compute(a):
+        nonlocal call_count
+        call_count += 1
+        return {"x": a + 1, "y": a * 2}
+
+    shared = model.param.a.rx.pipe(expensive_compute)
+
+    x_rx = shared.rx.pipe(lambda d: d["x"])
+    y_rx = shared.rx.pipe(lambda d: d["y"])
+
+    x_rx.rx.value
+    y_rx.rx.value
+
+    await async_wait_until(lambda: call_count == 1)
+
+    model.a = 2.0
+
+    x_rx.rx.value
+    y_rx.rx.value
+
+    await async_wait_until(lambda: call_count == 2)
+
+    assert x_rx.rx.value == 3
+    assert y_rx.rx.value == 4
+
+    assert call_count == 2

Reply via email to