Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-langgraph for 
openSUSE:Factory checked in at 2026-07-10 17:42:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langgraph (Old)
 and      /work/SRC/openSUSE:Factory/.python-langgraph.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-langgraph"

Fri Jul 10 17:42:44 2026 rev:3 rq:1364812 version:1.2.9

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-langgraph/python-langgraph.changes        
2026-07-07 21:07:22.506951432 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-langgraph.new.1991/python-langgraph.changes  
    2026-07-10 17:45:09.001090285 +0200
@@ -1,0 +2,6 @@
+Fri Jul 10 05:15:05 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 1.2.9:
+  * Bug-fix release; no API or dependency changes
+
+-------------------------------------------------------------------

Old:
----
  langgraph-1.2.8.tar.gz

New:
----
  langgraph-1.2.9.tar.gz

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

Other differences:
------------------
++++++ python-langgraph.spec ++++++
--- /var/tmp/diff_new_pack.FiNGog/_old  2026-07-10 17:45:11.909189903 +0200
+++ /var/tmp/diff_new_pack.FiNGog/_new  2026-07-10 17:45:11.921190314 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-langgraph
-Version:        1.2.8
+Version:        1.2.9
 Release:        0
 Summary:        Library for building stateful, multi-actor applications with 
LLMs
 License:        MIT

++++++ langgraph-1.2.8.tar.gz -> langgraph-1.2.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.8/PKG-INFO new/langgraph-1.2.9/PKG-INFO
--- old/langgraph-1.2.8/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: langgraph
-Version: 1.2.8
+Version: 1.2.9
 Summary: Building stateful, multi-actor applications with LLMs
 Project-URL: Homepage, https://docs.langchain.com/oss/python/langgraph/overview
 Project-URL: Documentation, https://reference.langchain.com/python/langgraph/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.8/langgraph/pregel/_checkpoint.py 
new/langgraph-1.2.9/langgraph/pregel/_checkpoint.py
--- old/langgraph-1.2.8/langgraph/pregel/_checkpoint.py 2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/langgraph/pregel/_checkpoint.py 2020-02-02 
01:00:00.000000000 +0100
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 import uuid
-from collections.abc import Callable, Mapping
+from collections.abc import Callable, Iterable, Mapping
 from datetime import datetime, timezone
 from typing import Any, cast
 
@@ -14,6 +14,7 @@
 from langgraph.checkpoint.serde.types import _DeltaSnapshot
 
 from langgraph._internal._config import DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT
+from langgraph._internal._constants import PUSH
 from langgraph._internal._typing import MISSING
 from langgraph.channels.base import BaseChannel
 from langgraph.channels.delta import DeltaChannel
@@ -70,6 +71,81 @@
     return result
 
 
+def get_updated_channels_from_tasks(
+    run_tasks: Iterable[Any],
+) -> set[str]:
+    """Channel names written by an update_state superstep (excluding PUSH)."""
+    return {c for task in run_tasks for c, _ in task.writes if c != PUSH}
+
+
+def get_delta_channels_from_all_channels(
+    channels: Mapping[str, BaseChannel],
+) -> set[str]:
+    """DeltaChannels to snapshot on the first update_state of a fresh 
thread."""
+    return {
+        k
+        for k, ch in channels.items()
+        if isinstance(ch, DeltaChannel) and ch.is_available()
+    }
+
+
+def create_metadata_for_update_state_api(
+    channels: Mapping[str, BaseChannel],
+    updated_channels: set[str],
+    *,
+    prev_metadata: Mapping[str, Any] | None,
+) -> dict[str, tuple[int, int]]:
+    """Advance ``counters_since_delta_snapshot`` for update_state on a 
non-fresh thread.
+
+    Mirrors the per-superstep counter bump in ``_loop._put_checkpoint``.
+    """
+    prev_counters = dict(
+        (prev_metadata or {}).get("counters_since_delta_snapshot") or {}
+    )
+    new_counters: dict[str, tuple[int, int]] = {}
+    for ch_name, ch in channels.items():
+        if not isinstance(ch, DeltaChannel):
+            continue
+        u, s = prev_counters.get(ch_name, (0, 0))
+        s += 1
+        if ch_name in updated_channels:
+            u += 1
+        new_counters[ch_name] = (u, s)
+    return new_counters
+
+
+def create_checkpoint_plan_for_update_state_api(
+    channels: Mapping[str, BaseChannel],
+    updated_channels: set[str],
+    *,
+    step: int,
+    parents: dict[str, Any],
+    saved_metadata: Mapping[str, Any] | None,
+    is_fresh_thread: bool,
+) -> tuple[set[str], dict[str, Any]]:
+    """Return ``(channels_to_snapshot, metadata)`` for an update_state head."""
+    metadata: dict[str, Any] = {
+        "source": "update",
+        "step": step,
+        "parents": parents,
+    }
+    if is_fresh_thread:
+        return get_delta_channels_from_all_channels(channels), metadata
+
+    new_counters = create_metadata_for_update_state_api(
+        channels,
+        updated_channels,
+        prev_metadata=saved_metadata,
+    )
+    channels_to_snapshot = delta_channels_to_snapshot(channels, new_counters)
+    for k in channels_to_snapshot:
+        new_counters[k] = (0, 0)
+    non_zero = {k: v for k, v in new_counters.items() if v != (0, 0)}
+    if non_zero:
+        metadata["counters_since_delta_snapshot"] = non_zero
+    return channels_to_snapshot, metadata
+
+
 def create_checkpoint(
     checkpoint: Checkpoint,
     channels: Mapping[str, BaseChannel] | None,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.8/langgraph/pregel/main.py 
new/langgraph-1.2.9/langgraph/pregel/main.py
--- old/langgraph-1.2.8/langgraph/pregel/main.py        2020-02-02 
01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/langgraph/pregel/main.py        2020-02-02 
01:00:00.000000000 +0100
@@ -108,7 +108,6 @@
     get_sync_graph_callback_manager_for_config,
 )
 from langgraph.channels.base import BaseChannel
-from langgraph.channels.delta import DeltaChannel
 from langgraph.channels.topic import Topic
 from langgraph.config import get_config
 from langgraph.constants import END
@@ -133,7 +132,9 @@
     channels_from_checkpoint,
     copy_checkpoint,
     create_checkpoint,
+    create_checkpoint_plan_for_update_state_api,
     empty_checkpoint,
+    get_updated_channels_from_tasks,
 )
 from langgraph.pregel._draw import draw_graph
 from langgraph.pregel._io import map_input, read_channels
@@ -1996,13 +1997,14 @@
                         },
                     ),
                 )
-            # save task writes
-            for task_id, task in zip(run_task_ids, run_tasks):
-                # channel writes are saved to current checkpoint
-                channel_writes = [w for w in task.writes if w[0] != PUSH]
-                if saved and channel_writes:
-                    checkpointer.put_writes(checkpoint_config, channel_writes, 
task_id)
-            # apply to checkpoint and save
+            updated_channels = get_updated_channels_from_tasks(run_tasks)
+            if saved is not None:
+                for task_id, task in zip(run_task_ids, run_tasks):
+                    channel_writes = [w for w in task.writes if w[0] != PUSH]
+                    if channel_writes:
+                        checkpointer.put_writes(
+                            checkpoint_config, channel_writes, task_id
+                        )
             apply_writes(
                 checkpoint,
                 channels,
@@ -2010,35 +2012,35 @@
                 checkpointer.get_next_version,
                 self.trigger_to_nodes,
             )
-            # On a fresh thread there is no ancestor to replay DeltaChannel
-            # writes from, so force a self-contained snapshot in the first
-            # checkpoint instead of relying on ancestor write-replay.
-            delta_snapshot = (
-                {
-                    k
-                    for k, ch in channels.items()
-                    if isinstance(ch, DeltaChannel) and ch.is_available()
-                }
-                if saved is None
-                else None
+            channels_to_snapshot, checkpoint_metadata = (
+                create_checkpoint_plan_for_update_state_api(
+                    channels,
+                    updated_channels,
+                    step=step + 1,
+                    parents=saved.metadata.get("parents", {}) if saved else {},
+                    saved_metadata=saved.metadata if saved else None,
+                    is_fresh_thread=saved is None,
+                )
             )
             checkpoint = create_checkpoint(
-                checkpoint, channels, step + 1, 
channels_to_snapshot=delta_snapshot
+                checkpoint,
+                channels,
+                step + 1,
+                updated_channels=updated_channels if channels_to_snapshot else 
None,
+                get_next_version=checkpointer.get_next_version
+                if channels_to_snapshot
+                else None,
+                channels_to_snapshot=channels_to_snapshot,
             )
             next_config = checkpointer.put(
                 checkpoint_config,
                 checkpoint,
-                {
-                    "source": "update",
-                    "step": step + 1,
-                    "parents": saved.metadata.get("parents", {}) if saved else 
{},
-                },
+                checkpoint_metadata,
                 get_new_channel_versions(
                     checkpoint_previous_versions, 
checkpoint["channel_versions"]
                 ),
             )
             for task_id, task in zip(run_task_ids, run_tasks):
-                # save push writes
                 if push_writes := [w for w in task.writes if w[0] == PUSH]:
                     checkpointer.put_writes(next_config, push_writes, task_id)
 
@@ -2455,15 +2457,14 @@
                         },
                     ),
                 )
-            # save task writes
-            for task_id, task in zip(run_task_ids, run_tasks):
-                # channel writes are saved to current checkpoint
-                channel_writes = [w for w in task.writes if w[0] != PUSH]
-                if saved and channel_writes:
-                    await checkpointer.aput_writes(
-                        checkpoint_config, channel_writes, task_id
-                    )
-            # apply to checkpoint and save
+            updated_channels = get_updated_channels_from_tasks(run_tasks)
+            if saved is not None:
+                for task_id, task in zip(run_task_ids, run_tasks):
+                    channel_writes = [w for w in task.writes if w[0] != PUSH]
+                    if channel_writes:
+                        await checkpointer.aput_writes(
+                            checkpoint_config, channel_writes, task_id
+                        )
             apply_writes(
                 checkpoint,
                 channels,
@@ -2471,36 +2472,35 @@
                 checkpointer.get_next_version,
                 self.trigger_to_nodes,
             )
-            # On a fresh thread there is no ancestor to replay DeltaChannel
-            # writes from, so force a self-contained snapshot in the first
-            # checkpoint instead of relying on ancestor write-replay.
-            delta_snapshot = (
-                {
-                    k
-                    for k, ch in channels.items()
-                    if isinstance(ch, DeltaChannel) and ch.is_available()
-                }
-                if saved is None
-                else None
+            channels_to_snapshot, checkpoint_metadata = (
+                create_checkpoint_plan_for_update_state_api(
+                    channels,
+                    updated_channels,
+                    step=step + 1,
+                    parents=saved.metadata.get("parents", {}) if saved else {},
+                    saved_metadata=saved.metadata if saved else None,
+                    is_fresh_thread=saved is None,
+                )
             )
             checkpoint = create_checkpoint(
-                checkpoint, channels, step + 1, 
channels_to_snapshot=delta_snapshot
+                checkpoint,
+                channels,
+                step + 1,
+                updated_channels=updated_channels if channels_to_snapshot else 
None,
+                get_next_version=checkpointer.get_next_version
+                if channels_to_snapshot
+                else None,
+                channels_to_snapshot=channels_to_snapshot,
             )
-            # save checkpoint, after applying writes
             next_config = await checkpointer.aput(
                 checkpoint_config,
                 checkpoint,
-                {
-                    "source": "update",
-                    "step": step + 1,
-                    "parents": saved.metadata.get("parents", {}) if saved else 
{},
-                },
+                checkpoint_metadata,
                 get_new_channel_versions(
                     checkpoint_previous_versions, 
checkpoint["channel_versions"]
                 ),
             )
             for task_id, task in zip(run_task_ids, run_tasks):
-                # save push writes
                 if push_writes := [w for w in task.writes if w[0] == PUSH]:
                     await checkpointer.aput_writes(next_config, push_writes, 
task_id)
             return patch_checkpoint_map(next_config, saved.metadata if saved 
else None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.8/pyproject.toml 
new/langgraph-1.2.9/pyproject.toml
--- old/langgraph-1.2.8/pyproject.toml  2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/pyproject.toml  2020-02-02 01:00:00.000000000 +0100
@@ -4,7 +4,7 @@
 
 [project]
 name = "langgraph"
-version = "1.2.8"
+version = "1.2.9"
 description = "Building stateful, multi-actor applications with LLMs"
 authors = []
 requires-python = ">=3.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langgraph-1.2.8/tests/test_delta_channel_update_state.py 
new/langgraph-1.2.9/tests/test_delta_channel_update_state.py
--- old/langgraph-1.2.8/tests/test_delta_channel_update_state.py        
2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/tests/test_delta_channel_update_state.py        
2020-02-02 01:00:00.000000000 +0100
@@ -1,23 +1,19 @@
 """Tests for `update_state` / `aupdate_state` against `DeltaChannel`.
 
-Originally a regression suite for deepagents#3774 — `update_state` on a *fresh*
-thread silently dropped the first write to a `DeltaChannel`-backed channel
-because channel writes were only persisted when a previous checkpoint existed
-and no snapshot was written either, so the checkpoint reconstructed to empty.
-
-Fixed by forcing a self-contained `_DeltaSnapshot` blob into the first
-checkpoint on a fresh thread (`saved is None`), so the value is stored inline
-and no ancestor write-replay is required. This keeps the read/replay path
-untouched.
+Regression suite for deepagents#3774 and Postgres read-path compatibility.
+
+Fresh-thread ``update_state`` force-snapshots DeltaChannels (1.2.8). Non-fresh
+``update_state`` persists ``checkpoint_writes`` on the parent, advances
+``counters_since_delta_snapshot`` on the new head, and snapshots when a
+channel reaches ``snapshot_frequency`` (mirroring normal run cadence).
 
 Coverage:
 
-* fresh-thread regression: single `update_state` writes a message and reads 
back
-* non-fresh thread: `update_state` after `invoke`, after another 
`update_state`,
-  and `bulk_update_state` with multiple per-superstep updates
-* update-by-id end-to-end via `update_state` (DeltaChannel reducer semantics)
-* state-history chain shape on a fresh thread (single self-contained update
-  checkpoint with the snapshot inline and no parent)
+* fresh-thread regression: single ``update_state`` writes a message and reads 
back
+* non-fresh thread: ``update_state`` after ``invoke``, after another 
``update_state``,
+  and ``bulk_update_state`` with multiple per-superstep updates
+* update-by-id end-to-end via ``update_state`` (DeltaChannel reducer semantics)
+* fresh-thread head is snapshotted; non-fresh heads carry delta replay counters
 """
 
 from typing import Annotated, Any
@@ -25,6 +21,7 @@
 import pytest
 from langchain_core.messages import HumanMessage
 from langgraph.checkpoint.memory import InMemorySaver
+from langgraph.checkpoint.serde.types import _DeltaSnapshot
 from typing_extensions import TypedDict
 
 from langgraph.channels.delta import DeltaChannel
@@ -34,13 +31,20 @@
 pytestmark = pytest.mark.anyio
 
 
-def _build_graph(checkpointer: InMemorySaver, *, two_nodes: bool = False) -> 
Any:
+def _build_graph(
+    checkpointer: InMemorySaver,
+    *,
+    two_nodes: bool = False,
+    snapshot_frequency: int = 1000,
+) -> Any:
     """Compile a minimal DeltaChannel-backed `messages` graph.
 
     `two_nodes=True` adds a second writer node so `bulk_update_state` can route
     distinct updates to different `as_node` values within a single superstep.
     """
-    channel = DeltaChannel(_messages_delta_reducer)
+    channel = DeltaChannel(
+        _messages_delta_reducer, snapshot_frequency=snapshot_frequency
+    )
     State = TypedDict("State", {"messages": Annotated[list, channel]})  # 
type: ignore[call-overload]  # noqa: UP013
 
     def model(state: dict) -> dict:
@@ -90,14 +94,30 @@
     assert [m.content for m in state.values["messages"]] == ["hello"]
 
 
+def test_fresh_update_state_head_snapshots_delta_channel() -> None:
+    saver = InMemorySaver()
+    graph = _build_graph(saver)
+    config = {"configurable": {"thread_id": "fresh-head-snapshot"}}
+
+    graph.update_state(
+        config,
+        {"messages": [HumanMessage(content="hello", id="m1")]},
+        as_node="model",
+    )
+
+    head = saver.get_tuple(config)
+    assert head is not None
+    assert isinstance(head.checkpoint["channel_values"].get("messages"), 
_DeltaSnapshot)
+    assert head.metadata is not None
+    assert "counters_since_delta_snapshot" not in head.metadata
+
+
 # ---------------------------------------------------------------------------
 # Non-fresh thread: update_state after invoke
 # ---------------------------------------------------------------------------
 
 
 def test_update_state_after_invoke_delta_channel() -> None:
-    """The non-fresh-thread path was already working before the fix; pin it
-    down so the forced-snapshot change for fresh threads doesn't regress it."""
     saver = InMemorySaver()
     graph = _build_graph(saver)
     config = {"configurable": {"thread_id": "after-invoke-sync"}}
@@ -113,6 +133,12 @@
     assert [m.content for m in state.values["messages"]] == ["seed", 
"appended"]
     assert [m.id for m in state.values["messages"]] == ["m1", "m2"]
 
+    head = saver.get_tuple(config)
+    assert head is not None
+    assert "messages" not in head.checkpoint["channel_values"]
+    assert head.metadata is not None
+    assert head.metadata["counters_since_delta_snapshot"]["messages"] == [2, 4]
+
 
 async def test_aupdate_state_after_invoke_delta_channel() -> None:
     saver = InMemorySaver()
@@ -136,9 +162,6 @@
 
 
 def test_consecutive_update_states_delta_channel() -> None:
-    """First update_state forces a self-contained snapshot seed; the second
-    sees a real parent (`saved is not None`) and anchors its writes under that
-    seed. Both messages must round-trip in chronological order."""
     saver = InMemorySaver()
     graph = _build_graph(saver)
     config = {"configurable": {"thread_id": "consecutive-sync"}}
@@ -158,6 +181,39 @@
     assert [m.content for m in state.values["messages"]] == ["first", "second"]
     assert [m.id for m in state.values["messages"]] == ["m1", "m2"]
 
+    head = saver.get_tuple(config)
+    assert head is not None
+    assert "messages" not in head.checkpoint["channel_values"]
+    assert head.metadata is not None
+    assert head.metadata["counters_since_delta_snapshot"]["messages"] == [1, 1]
+
+
+def test_update_state_snapshots_at_frequency() -> None:
+    """Non-fresh update_state snapshots when counters reach 
snapshot_frequency."""
+    saver = InMemorySaver()
+    graph = _build_graph(saver, snapshot_frequency=1)
+    config = {"configurable": {"thread_id": "snapshot-at-freq"}}
+
+    graph.update_state(
+        config,
+        {"messages": [HumanMessage(content="first", id="m1")]},
+        as_node="model",
+    )
+    graph.update_state(
+        config,
+        {"messages": [HumanMessage(content="second", id="m2")]},
+        as_node="model",
+    )
+
+    state = graph.get_state(config)
+    assert [m.content for m in state.values["messages"]] == ["first", "second"]
+
+    head = saver.get_tuple(config)
+    assert head is not None
+    assert isinstance(head.checkpoint["channel_values"].get("messages"), 
_DeltaSnapshot)
+    assert head.metadata is not None
+    assert "counters_since_delta_snapshot" not in head.metadata
+
 
 async def test_aconsecutive_update_states_delta_channel() -> None:
     saver = InMemorySaver()
@@ -255,7 +311,7 @@
 
 
 # ---------------------------------------------------------------------------
-# Public-API observation of the forced-snapshot mechanism
+# Public-API observation of fresh-thread checkpoint shape
 # ---------------------------------------------------------------------------
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langgraph-1.2.8/uv.lock new/langgraph-1.2.9/uv.lock
--- old/langgraph-1.2.8/uv.lock 2020-02-02 01:00:00.000000000 +0100
+++ new/langgraph-1.2.9/uv.lock 2020-02-02 01:00:00.000000000 +0100
@@ -1438,7 +1438,7 @@
 
 [[package]]
 name = "langgraph"
-version = "1.2.8"
+version = "1.2.9"
 source = { editable = "." }
 dependencies = [
     { name = "langchain-core" },

Reply via email to