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-10-27 16:58:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-langgraph (Old) and /work/SRC/openSUSE:Factory/.python-langgraph.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langgraph" Tue Oct 27 16:58:19 2026 rev:13 rq:1392919 version:1.2.18 Changes: -------- --- /work/SRC/openSUSE:Factory/python-langgraph/python-langgraph.changes 2026-10-26 10:04:32.855150510 +0100 +++ /work/SRC/openSUSE:Factory/.python-langgraph.new.2252680/python-langgraph.changes 2026-10-27 16:58:20.952069301 +0100 @@ -1,0 +2,12 @@ +Tue Oct 27 06:32:08 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to 1.2.18: + * Default delta-channel snapshots to every 100 updates + instead of 1000 to bound replay latency + * Let node error_handler recover on the executor path and + stream failed task results + * Stream the last checkpoint of a run stopped by + interrupt_after or the recursion limit + * Stop duplicating tags at every subgraph level + +------------------------------------------------------------------- Old: ---- langgraph-1.2.17.tar.gz New: ---- langgraph-1.2.18.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-langgraph.spec ++++++ --- /var/tmp/diff_new_pack.XFVeiP/_old 2026-10-27 16:58:22.428131077 +0100 +++ /var/tmp/diff_new_pack.XFVeiP/_new 2026-10-27 16:58:22.430131160 +0100 @@ -17,7 +17,7 @@ Name: python-langgraph -Version: 1.2.17 +Version: 1.2.18 Release: 0 Summary: Library for building stateful, multi-actor applications with LLMs License: MIT ++++++ langgraph-1.2.17.tar.gz -> langgraph-1.2.18.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/PKG-INFO new/langgraph-1.2.18/PKG-INFO --- old/langgraph-1.2.17/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.5 Name: langgraph -Version: 1.2.17 +Version: 1.2.18 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.17/langgraph/_internal/_config.py new/langgraph-1.2.18/langgraph/_internal/_config.py --- old/langgraph-1.2.17/langgraph/_internal/_config.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/langgraph/_internal/_config.py 2020-02-02 01:00:00.000000000 +0100 @@ -347,6 +347,7 @@ recursion_limit=DEFAULT_RECURSION_LIMIT, configurable={}, ) + inherited_tags: set[str] = set() if var_config := var_child_runnable_config.get(): empty.update( { @@ -355,6 +356,7 @@ if _is_not_empty(v) }, ) + inherited_tags.update(empty["tags"]) # An explicit config that supplies its own checkpoint coordinate (a # thread_id, or any checkpoint_ns/checkpoint_id/checkpoint_map) is addressing # its own checkpoint lineage, so drop the inherited ambient configurable @@ -407,12 +409,14 @@ elif k == "tags": # Concatenate tags across configs so values bound via # with_config(...) are preserved when later configs - # supply additional tags. Matches merge_configs. + # supply additional tags. Matches merge_configs. Tags the + # ambient config already has are skipped: a subgraph gets + # its parent's config both explicitly and as the ambient + # one, so they would double at every nesting level. existing_tags: list[str] | None = empty.get("tags") + new_tags = [t for t in cast(list, v) if t not in inherited_tags] empty["tags"] = ( - [*existing_tags, *cast(list, v)] - if existing_tags - else list(cast(list, v)) + [*existing_tags, *new_tags] if existing_tags else new_tags ) else: empty[k] = v # type: ignore[literal-required] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/langgraph/channels/delta.py new/langgraph-1.2.18/langgraph/channels/delta.py --- old/langgraph-1.2.17/langgraph/channels/delta.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/langgraph/channels/delta.py 2020-02-02 01:00:00.000000000 +0100 @@ -60,7 +60,7 @@ typ: The value type (e.g. `list`, `dict`). Inferred automatically from the outer type when used inside `Annotated[T, DeltaChannel(...)]`. snapshot_frequency: Every Nth update to this channel writes a snapshot - blob (default `1000`). Must be a positive int. + blob (default `100`). Must be a positive int. """ __slots__ = ("value", "reducer", "snapshot_frequency") @@ -71,7 +71,7 @@ reducer: Callable[[Any, Sequence[Any]], Any], typ: type[Value] | None = None, *, - snapshot_frequency: int = 1000, + snapshot_frequency: int = 100, ) -> None: if snapshot_frequency <= 0: raise ValueError( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_executor.py new/langgraph-1.2.18/langgraph/pregel/_executor.py --- old/langgraph-1.2.17/langgraph/pregel/_executor.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/langgraph/pregel/_executor.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,6 +3,7 @@ import asyncio import concurrent.futures import time +import weakref from collections.abc import Awaitable, Callable, Coroutine from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack from contextvars import copy_context @@ -37,6 +38,13 @@ ) -> concurrent.futures.Future[T]: ... +# Futures whose exception a node's error handler took over; the executors +# don't re-raise them on exit. +SKIP_RERAISE_SET: weakref.WeakSet[concurrent.futures.Future | asyncio.Future] = ( + weakref.WeakSet() +) + + class BackgroundExecutor(AbstractContextManager): """A context manager that runs sync tasks in the background. Uses a thread pool executor to delegate tasks to separate threads. @@ -111,7 +119,7 @@ if exc_type is None: # re-raise the first exception that occurred in a task for task, (_, reraise) in tasks.items(): - if not reraise: + if not reraise or task in SKIP_RERAISE_SET: continue try: task.result() @@ -202,7 +210,7 @@ if exc_type is None: # re-raise the first exception that occurred in a task for task, (_, reraise) in tasks.items(): - if not reraise: + if not reraise or task in SKIP_RERAISE_SET: continue try: if exc := task.exception(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_loop.py new/langgraph-1.2.18/langgraph/pregel/_loop.py --- old/langgraph-1.2.17/langgraph/pregel/_loop.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/langgraph/pregel/_loop.py 2020-02-02 01:00:00.000000000 +0100 @@ -657,48 +657,11 @@ # check if iteration limit is reached if self.step > self.stop: self.status = "out_of_steps" + # stream the checkpoint the last step saved, as a tick that runs would + self._prepare_tasks_and_emit_checkpoint() return False - # prepare next tasks - self.tasks = prepare_next_tasks( - self.checkpoint, - self.checkpoint_pending_writes, - self.nodes, - self.channels, - self.managed, - self.config, - self.step, - self.stop, - for_execution=True, - manager=self.manager, - store=self.store, - checkpointer=self.checkpointer, - trigger_to_nodes=self.trigger_to_nodes, - updated_channels=self.updated_channels, - retry_policy=self.retry_policy, - cache_policy=self.cache_policy, - ) - - # produce debug output - if self._checkpointer_put_after_previous is not None: - self._emit( - "checkpoints", - map_debug_checkpoint, - { - **self.checkpoint_config, - CONF: { - **self.checkpoint_config[CONF], - CONFIG_KEY_CHECKPOINT_ID: self.checkpoint["id"], - }, - }, - self.channels, - self.stream_keys, - self.checkpoint_metadata, - self.tasks.values(), - self.checkpoint_pending_writes, - self.prev_checkpoint_config, - self.output_keys, - ) + self._prepare_tasks_and_emit_checkpoint() # if no more tasks, we're done if not self.tasks: @@ -806,10 +769,53 @@ self.checkpoint, self.interrupt_after, self.tasks.values() ): self.status = "interrupt_after" + # the next tick, which streams this checkpoint, never runs + self._prepare_tasks_and_emit_checkpoint() raise GraphInterrupt() # unset resuming flag self.config[CONF].pop(CONFIG_KEY_RESUMING, None) + def _prepare_tasks_and_emit_checkpoint(self) -> None: + self.tasks = prepare_next_tasks( + self.checkpoint, + self.checkpoint_pending_writes, + self.nodes, + self.channels, + self.managed, + self.config, + self.step, + self.stop, + for_execution=True, + manager=self.manager, + store=self.store, + checkpointer=self.checkpointer, + trigger_to_nodes=self.trigger_to_nodes, + updated_channels=self.updated_channels, + retry_policy=self.retry_policy, + cache_policy=self.cache_policy, + ) + + # produce debug output + if self._checkpointer_put_after_previous is not None: + self._emit( + "checkpoints", + map_debug_checkpoint, + { + **self.checkpoint_config, + CONF: { + **self.checkpoint_config[CONF], + CONFIG_KEY_CHECKPOINT_ID: self.checkpoint["id"], + }, + }, + self.channels, + self.stream_keys, + self.checkpoint_metadata, + self.tasks.values(), + self.checkpoint_pending_writes, + self.prev_checkpoint_config, + self.output_keys, + ) + def match_cached_writes(self) -> Sequence[PregelExecutableTask]: raise NotImplementedError diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/langgraph/pregel/_runner.py new/langgraph-1.2.18/langgraph/pregel/_runner.py --- old/langgraph-1.2.17/langgraph/pregel/_runner.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/langgraph/pregel/_runner.py 2020-02-02 01:00:00.000000000 +0100 @@ -43,7 +43,7 @@ from langgraph.constants import TAG_HIDDEN from langgraph.errors import GraphBubbleUp, GraphInterrupt, is_invalid_resume from langgraph.pregel._algo import Call -from langgraph.pregel._executor import Submit +from langgraph.pregel._executor import SKIP_RERAISE_SET, Submit from langgraph.pregel._retry import arun_with_retry, run_with_retry from langgraph.pregel._task_status import CONTROL_WRITES from langgraph.types import ( @@ -68,10 +68,6 @@ "concurrent/futures/_base.py", ) -SKIP_RERAISE_SET: weakref.WeakSet[concurrent.futures.Future | asyncio.Future] = ( - weakref.WeakSet() -) - class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]): event: E @@ -239,6 +235,8 @@ fut.set_exception(exc) futures.done.add(fut) elif reraise and id(exc) not in self._handled_exception_ids: + # let the caller stream this task's result before it raises + yield if tb := exc.__traceback__: while tb.tb_next is not None and any( tb.tb_frame.f_code.co_filename.endswith(name) @@ -430,6 +428,8 @@ fut.set_exception(exc) futures.done.add(fut) elif reraise and id(exc) not in self._handled_exception_ids: + # let the caller stream this task's result before it raises + yield if tb := exc.__traceback__: while tb.tb_next is not None and any( tb.tb_frame.f_code.co_filename.endswith(name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/pyproject.toml new/langgraph-1.2.18/pyproject.toml --- old/langgraph-1.2.17/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -4,7 +4,7 @@ [project] name = "langgraph" -version = "1.2.17" +version = "1.2.18" 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.17/tests/test_channels.py new/langgraph-1.2.18/tests/test_channels.py --- old/langgraph-1.2.17/tests/test_channels.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/tests/test_channels.py 2020-02-02 01:00:00.000000000 +0100 @@ -20,6 +20,7 @@ from langgraph.graph import START, StateGraph from langgraph.graph.message import _messages_delta_reducer from langgraph.graph.state import _get_channel +from langgraph.pregel._checkpoint import delta_channels_to_snapshot from langgraph.types import Overwrite pytestmark = pytest.mark.anyio @@ -342,6 +343,26 @@ # --------------------------------------------------------------------------- [email protected]("frequency", [None, 50, 1000]) +def test_delta_channel_snapshot_frequency(frequency: int | None) -> None: + spec = ( + DeltaChannel(_messages_delta_reducer) + if frequency is None + else DeltaChannel(_messages_delta_reducer, snapshot_frequency=frequency) + ) + expected = 100 if frequency is None else frequency + assert spec.snapshot_frequency == expected + channel = spec.from_checkpoint(MISSING) + channel.update([HumanMessage(content="hi", id="h1")]) + assert channel.copy().snapshot_frequency == expected + for updates in (expected - 1, expected): + assert delta_channels_to_snapshot( + {"messages": channel}, + {"messages": (updates, updates)}, + {"messages": 1}, + ) == ({"messages"} if updates == expected else set()) + + def test_delta_channel_snapshot_version_based() -> None: """Snapshots fire when a channel accumulates `snapshot_frequency` updates. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/tests/test_pregel.py new/langgraph-1.2.18/tests/test_pregel.py --- old/langgraph-1.2.17/tests/test_pregel.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/tests/test_pregel.py 2020-02-02 01:00:00.000000000 +0100 @@ -4346,6 +4346,25 @@ assert graph.invoke({"foo": "hello"}) == {"foo": "hello", "bar": "hello!"} [email protected]("stream_mode", ["tasks", "debug"]) +def test_failing_task_result_is_streamed_before_the_error(stream_mode: str) -> None: + class State(TypedDict): + x: str + + def failing(state: State) -> State: + raise ValueError("boom") + + graph = StateGraph(State).add_node("failing", failing).add_edge(START, "failing") + errors = [] + with pytest.raises(ValueError, match="boom"): + for chunk in graph.compile().stream({"x": ""}, stream_mode=stream_mode): + payload = chunk["payload"] if stream_mode == "debug" else chunk + if "error" in payload: + errors.append(payload["error"]) + + assert [type(error) for error in errors] == [ValueError] + + def test_debug_retry(sync_checkpointer: BaseCheckpointSaver): class State(TypedDict): messages: Annotated[list[str], operator.add] @@ -4410,6 +4429,52 @@ assert stream_parent_conf == history_parent_conf +def test_interrupt_after_streams_the_checkpoint_it_pauses_on( + sync_checkpointer: BaseCheckpointSaver, +) -> None: + class State(TypedDict): + x: int + + graph = ( + StateGraph(State) + .add_node("one", lambda state: {"x": 1}) + .add_node("two", lambda state: {"x": 2}) + .add_edge(START, "one") + .add_edge("one", "two") + .compile(checkpointer=sync_checkpointer, interrupt_after=["one"]) + ) + config = {"configurable": {"thread_id": "1"}} + + checkpoints = list(graph.stream({"x": 0}, config, stream_mode="checkpoints")) + + state = graph.get_state(config) + assert checkpoints[-1]["config"] == state.config + assert checkpoints[-1]["next"] == ["two"] + + +def test_recursion_limit_streams_the_last_checkpoint_saved( + sync_checkpointer: BaseCheckpointSaver, +) -> None: + class State(TypedDict): + x: int + + graph = ( + StateGraph(State) + .add_node("loop", lambda state: {"x": state["x"] + 1}) + .add_edge(START, "loop") + .add_edge("loop", "loop") + .compile(checkpointer=sync_checkpointer) + ) + config = {"configurable": {"thread_id": "1"}, "recursion_limit": 3} + checkpoints = [] + + with pytest.raises(GraphRecursionError): + for chunk in graph.stream({"x": 0}, config, stream_mode="checkpoints"): + checkpoints.append(chunk) + + assert checkpoints[-1]["config"] == graph.get_state(config).config + + def test_debug_subgraphs( sync_checkpointer: BaseCheckpointSaver, durability: Durability ): @@ -6880,6 +6945,22 @@ ] +def test_invoke_tags_reach_a_node_three_subgraphs_deep_once() -> None: + class State(TypedDict): + copies: int + + def leaf(state: State, config: RunnableConfig) -> State: + return {"copies": config["tags"].count("my-tag")} + + graph = StateGraph(State).add_node("leaf", leaf).add_edge(START, "leaf").compile() + for _ in range(3): + graph = ( + StateGraph(State).add_node("sub", graph).add_edge(START, "sub").compile() + ) + + assert graph.invoke({"copies": 0}, {"tags": ["my-tag"]}) == {"copies": 1} + + def test_tags_stream_mode_messages() -> None: model = GenericFakeChatModel(messages=iter(["foo"]), tags=["meow"]) graph = ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/tests/test_pregel_async.py new/langgraph-1.2.18/tests/test_pregel_async.py --- old/langgraph-1.2.17/tests/test_pregel_async.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/tests/test_pregel_async.py 2020-02-02 01:00:00.000000000 +0100 @@ -5809,6 +5809,60 @@ ) # still overwriting the same one [email protected]("stream_mode", ["tasks", "debug"]) +async def test_failing_task_result_is_streamed_before_the_error( + stream_mode: str, +) -> None: + class State(TypedDict): + x: str + + async def failing(state: State) -> State: + raise ValueError("boom") + + graph = StateGraph(State).add_node("failing", failing).add_edge(START, "failing") + errors = [] + with pytest.raises(ValueError, match="boom"): + async for chunk in graph.compile().astream({"x": ""}, stream_mode=stream_mode): + payload = chunk["payload"] if stream_mode == "debug" else chunk + if "error" in payload: + errors.append(payload["error"]) + + assert [type(error) for error in errors] == [ValueError] + + [email protected]( + ("parallel", "stream_mode"), [(False, "custom"), (True, "values")] +) +async def test_graph_error_handler_recovers_on_the_executor_path( + parallel: bool, stream_mode: str +) -> None: + class State(TypedDict): + x: str + + async def failing(state: State) -> State: + raise ValueError("boom") + + async def handler(state: State) -> Command: + return Command(update={"x": "handled"}, goto=END) + + builder = ( + StateGraph(State) + .add_node("failing", failing, error_handler=handler) + .add_edge(START, "failing") + ) + if parallel: + builder.add_node("sibling", lambda state: {}).add_edge(START, "sibling") + + chunks = [ + chunk + async for chunk in builder.compile().astream( + {"x": ""}, stream_mode=[stream_mode, "values"] + ) + ] + + assert [c[-1] for c in chunks if c[0] == "values"][-1] == {"x": "handled"} + + async def test_debug_retry(async_checkpointer: BaseCheckpointSaver): class State(TypedDict): messages: Annotated[list[str], operator.add] @@ -5875,6 +5929,55 @@ assert stream_parent_conf == history_parent_conf +async def test_interrupt_after_streams_the_checkpoint_it_pauses_on( + async_checkpointer: BaseCheckpointSaver, +) -> None: + class State(TypedDict): + x: int + + graph = ( + StateGraph(State) + .add_node("one", lambda state: {"x": 1}) + .add_node("two", lambda state: {"x": 2}) + .add_edge(START, "one") + .add_edge("one", "two") + .compile(checkpointer=async_checkpointer, interrupt_after=["one"]) + ) + config = {"configurable": {"thread_id": "1"}} + + checkpoints = [ + chunk + async for chunk in graph.astream({"x": 0}, config, stream_mode="checkpoints") + ] + + state = await graph.aget_state(config) + assert checkpoints[-1]["config"] == state.config + assert checkpoints[-1]["next"] == ["two"] + + +async def test_recursion_limit_streams_the_last_checkpoint_saved( + async_checkpointer: BaseCheckpointSaver, +) -> None: + class State(TypedDict): + x: int + + graph = ( + StateGraph(State) + .add_node("loop", lambda state: {"x": state["x"] + 1}) + .add_edge(START, "loop") + .add_edge("loop", "loop") + .compile(checkpointer=async_checkpointer) + ) + config = {"configurable": {"thread_id": "1"}, "recursion_limit": 3} + checkpoints = [] + + with pytest.raises(GraphRecursionError): + async for chunk in graph.astream({"x": 0}, config, stream_mode="checkpoints"): + checkpoints.append(chunk) + + assert checkpoints[-1]["config"] == (await graph.aget_state(config)).config + + async def test_debug_subgraphs( async_checkpointer: BaseCheckpointSaver, durability: Durability ): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/tests/test_retry.py new/langgraph-1.2.18/tests/test_retry.py --- old/langgraph-1.2.17/tests/test_retry.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/tests/test_retry.py 2020-02-02 01:00:00.000000000 +0100 @@ -2097,6 +2097,47 @@ assert result["foo"] == "handled_next" +# Each case runs the failing node on the executor path instead of the +# single-task fast path. [email protected]( + ("parallel", "stream_mode", "subgraphs"), + [ + (False, "custom", False), + (False, "messages", False), + (False, "values", True), + (True, "values", False), + ], +) +def test_graph_error_handler_recovers_on_the_executor_path( + parallel: bool, stream_mode: str, subgraphs: bool +) -> None: + class State(TypedDict): + x: str + + def failing(state: State) -> State: + raise ValueError("boom") + + def handler(state: State) -> Command: + return Command(update={"x": "handled"}, goto=END) + + builder = ( + StateGraph(State) + .add_node("failing", failing, error_handler=handler) + .add_edge(START, "failing") + ) + if parallel: + builder.add_node("sibling", lambda state: {}).add_edge(START, "sibling") + graph = builder.compile() + + chunks = list( + graph.stream( + {"x": ""}, stream_mode=[stream_mode, "values"], subgraphs=subgraphs + ) + ) + + assert [c[-1] for c in chunks if c[-2] == "values"][-1] == {"x": "handled"} + + def test_graph_error_handler_failure_fails_run(): class State(TypedDict): foo: str diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/tests/test_utils.py new/langgraph-1.2.18/tests/test_utils.py --- old/langgraph-1.2.17/tests/test_utils.py 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/tests/test_utils.py 2020-02-02 01:00:00.000000000 +0100 @@ -799,6 +799,15 @@ assert merged["tags"] == ["shared", "alpha", "shared", "beta"] +def test_ensure_config_skips_tags_inherited_from_ambient() -> None: + token = var_child_runnable_config.set({"tags": ["inherited"]}) + try: + merged = ensure_config({"tags": ["inherited", "own"]}) + finally: + var_child_runnable_config.reset(token) + assert merged["tags"] == ["inherited", "own"] + + def test_ensure_config_merges_callbacks_across_configs() -> None: a_cb = _TrackingCB("a") b_cb = _TrackingCB("b") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/langgraph-1.2.17/uv.lock new/langgraph-1.2.18/uv.lock --- old/langgraph-1.2.17/uv.lock 2020-02-02 01:00:00.000000000 +0100 +++ new/langgraph-1.2.18/uv.lock 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15'", @@ -932,18 +932,6 @@ ] [[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - -[[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } @@ -1451,7 +1439,7 @@ [[package]] name = "langgraph" -version = "1.2.17" +version = "1.2.18" source = { editable = "." } dependencies = [ { name = "langchain-core" }, @@ -1637,7 +1625,7 @@ [[package]] name = "langgraph-checkpoint" -version = "4.3.0" +version = "4.3.1" source = { editable = "../checkpoint" } dependencies = [ { name = "langchain-core" }, @@ -2111,7 +2099,7 @@ [[package]] name = "nbconvert" -version = "7.17.1" +version = "7.17.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -2129,9 +2117,9 @@ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/6a/252c7620305a9661205aed7a2a4b3592f5207bfca56ad11b7dea2c5ec2f6/nbconvert-7.17.2.tar.gz", hash = "sha256:344a218e8595117ce37396ed025d994f146d076226ebac59255a2fa085ab24d8", size = 865989, upload-time = "2026-10-12T09:11:19.583Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" }, + { url = "https://files.pythonhosted.org/packages/81/fd/a8d457af68848a8e5c12dd1d84ff35c415db471af78280e0703db4efbb12/nbconvert-7.17.2-py3-none-any.whl", hash = "sha256:6a143c11c00bf831604da4b1be4347d36ea52367057eb920135d69bc2862fed2", size = 262094, upload-time = "2026-10-12T09:11:17.215Z" }, ] [[package]] @@ -2189,84 +2177,114 @@ [[package]] name = "opentelemetry-api" -version = "1.39.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "opentelemetry-exporter-http-transport" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.39.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, + { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.39.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, + { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.39.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.39.1" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.60b1" +version = "0.66b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, ] [[package]] @@ -4240,15 +4258,6 @@ ] [[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] - -[[package]] name = "zstandard" version = "0.25.0" source = { registry = "https://pypi.org/simple" }
