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" },