From 1a477e57ff29131c91a9a4f792b80107a1481245 Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 21 Apr 2025 20:43:40 -0700 Subject: [PATCH 1/5] upgrade to py39 standards --- libs/langgraph/Makefile | 2 +- libs/langgraph/bench/pydantic_state.py | 3 +- libs/langgraph/bench/wide_dict.py | 3 +- libs/langgraph/bench/wide_state.py | 3 +- libs/langgraph/langgraph/_api/deprecation.py | 4 +- .../langgraph/langgraph/channels/any_value.py | 7 +- libs/langgraph/langgraph/channels/base.py | 3 +- libs/langgraph/langgraph/channels/binop.py | 9 +- .../channels/dynamic_barrier_value.py | 9 +- .../langgraph/channels/ephemeral_value.py | 7 +- .../langgraph/channels/last_value.py | 7 +- .../langgraph/channels/named_barrier_value.py | 9 +- libs/langgraph/langgraph/channels/topic.py | 5 +- .../langgraph/channels/untracked_value.py | 9 +- libs/langgraph/langgraph/constants.py | 3 +- libs/langgraph/langgraph/errors.py | 3 +- libs/langgraph/langgraph/func/__init__.py | 3 +- libs/langgraph/langgraph/graph/branch.py | 9 +- libs/langgraph/langgraph/graph/graph.py | 6 +- libs/langgraph/langgraph/graph/message.py | 2 +- .../langgraph/langgraph/graph/schema_utils.py | 13 +- libs/langgraph/langgraph/graph/state.py | 49 ++-- libs/langgraph/langgraph/managed/base.py | 13 +- libs/langgraph/langgraph/managed/context.py | 31 ++- .../langgraph/managed/shared_value.py | 7 +- libs/langgraph/langgraph/pregel/__init__.py | 260 +++++++++--------- libs/langgraph/langgraph/pregel/algo.py | 4 +- libs/langgraph/langgraph/pregel/call.py | 11 +- libs/langgraph/langgraph/pregel/checkpoint.py | 3 +- libs/langgraph/langgraph/pregel/debug.py | 5 +- libs/langgraph/langgraph/pregel/executor.py | 11 +- libs/langgraph/langgraph/pregel/io.py | 3 +- libs/langgraph/langgraph/pregel/loop.py | 37 ++- libs/langgraph/langgraph/pregel/manager.py | 3 +- libs/langgraph/langgraph/pregel/messages.py | 14 +- libs/langgraph/langgraph/pregel/protocol.py | 4 +- libs/langgraph/langgraph/pregel/read.py | 98 +++---- libs/langgraph/langgraph/pregel/remote.py | 4 +- libs/langgraph/langgraph/pregel/retry.py | 3 +- libs/langgraph/langgraph/pregel/runner.py | 11 +- libs/langgraph/langgraph/pregel/validate.py | 3 +- libs/langgraph/langgraph/pregel/write.py | 23 +- libs/langgraph/langgraph/types.py | 6 +- libs/langgraph/langgraph/utils/config.py | 3 +- libs/langgraph/langgraph/utils/fields.py | 9 +- libs/langgraph/langgraph/utils/future.py | 3 +- libs/langgraph/langgraph/utils/pydantic.py | 4 +- libs/langgraph/langgraph/utils/runnable.py | 17 +- libs/langgraph/pyproject.toml | 3 +- libs/langgraph/tests/any_str.py | 3 +- libs/langgraph/tests/conftest.py | 3 +- libs/langgraph/tests/fake_chat.py | 15 +- libs/langgraph/tests/test_channels.py | 3 +- .../tests/test_checkpoint_migration.py | 6 +- libs/langgraph/tests/test_io.py | 2 +- libs/langgraph/tests/test_large_cases.py | 9 +- .../langgraph/tests/test_large_cases_async.py | 4 +- libs/langgraph/tests/test_pregel.py | 35 +-- libs/langgraph/tests/test_pregel_async.py | 17 +- libs/langgraph/tests/test_retry.py | 12 +- libs/langgraph/tests/test_runnable.py | 8 +- libs/langgraph/tests/test_state.py | 4 +- libs/langgraph/tests/test_tracing_interops.py | 4 +- libs/langgraph/tests/test_utils.py | 15 +- 64 files changed, 428 insertions(+), 473 deletions(-) diff --git a/libs/langgraph/Makefile b/libs/langgraph/Makefile index 2c98201db..5ee1afa24 100644 --- a/libs/langgraph/Makefile +++ b/libs/langgraph/Makefile @@ -78,7 +78,7 @@ test_watch_all: PYTHON_FILES=. MYPY_CACHE=.mypy_cache lint format: PYTHON_FILES=. -lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$') +lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E r'\.py$$|\.ipynb$$') lint_package: PYTHON_FILES=langgraph lint_tests: PYTHON_FILES=tests lint_tests: MYPY_CACHE=.mypy_cache_test diff --git a/libs/langgraph/bench/pydantic_state.py b/libs/langgraph/bench/pydantic_state.py index 50874eed8..7c5fd0e0e 100644 --- a/libs/langgraph/bench/pydantic_state.py +++ b/libs/langgraph/bench/pydantic_state.py @@ -1,7 +1,8 @@ import operator +from collections.abc import Sequence from functools import partial from random import choice -from typing import Annotated, Optional, Sequence +from typing import Annotated, Optional from pydantic import BaseModel, Field, field_validator diff --git a/libs/langgraph/bench/wide_dict.py b/libs/langgraph/bench/wide_dict.py index 2f0df75ca..79549346c 100644 --- a/libs/langgraph/bench/wide_dict.py +++ b/libs/langgraph/bench/wide_dict.py @@ -1,7 +1,8 @@ import operator +from collections.abc import Sequence from functools import partial from random import choice -from typing import Annotated, Optional, Sequence +from typing import Annotated, Optional from typing_extensions import TypedDict diff --git a/libs/langgraph/bench/wide_state.py b/libs/langgraph/bench/wide_state.py index b331be6ec..04c50632a 100644 --- a/libs/langgraph/bench/wide_state.py +++ b/libs/langgraph/bench/wide_state.py @@ -1,8 +1,9 @@ import operator +from collections.abc import Sequence from dataclasses import dataclass, field from functools import partial from random import choice -from typing import Annotated, Optional, Sequence +from typing import Annotated, Optional from langgraph.constants import END, START from langgraph.graph.state import StateGraph diff --git a/libs/langgraph/langgraph/_api/deprecation.py b/libs/langgraph/langgraph/_api/deprecation.py index c93e09de8..3a0378a9f 100644 --- a/libs/langgraph/langgraph/_api/deprecation.py +++ b/libs/langgraph/langgraph/_api/deprecation.py @@ -1,6 +1,6 @@ import functools import warnings -from typing import Any, Callable, Type, TypeVar, Union, cast +from typing import Any, Callable, TypeVar, Union, cast class LangGraphDeprecationWarning(DeprecationWarning): @@ -8,7 +8,7 @@ class LangGraphDeprecationWarning(DeprecationWarning): F = TypeVar("F", bound=Callable[..., Any]) -C = TypeVar("C", bound=Type[Any]) +C = TypeVar("C", bound=type[Any]) def deprecated( diff --git a/libs/langgraph/langgraph/channels/any_value.py b/libs/langgraph/langgraph/channels/any_value.py index 51493ab19..ec597dacb 100644 --- a/libs/langgraph/langgraph/channels/any_value.py +++ b/libs/langgraph/langgraph/channels/any_value.py @@ -1,4 +1,5 @@ -from typing import Any, Generic, Sequence, Type +from collections.abc import Sequence +from typing import Any, Generic from typing_extensions import Self @@ -21,12 +22,12 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]): return isinstance(value, AnyValue) @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/base.py b/libs/langgraph/langgraph/channels/base.py index dc8888acd..230aa4096 100644 --- a/libs/langgraph/langgraph/channels/base.py +++ b/libs/langgraph/langgraph/channels/base.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Any, Generic, Sequence, TypeVar +from collections.abc import Sequence +from typing import Any, Generic, TypeVar from typing_extensions import Self diff --git a/libs/langgraph/langgraph/channels/binop.py b/libs/langgraph/langgraph/channels/binop.py index 1f95d5562..e974c5fba 100644 --- a/libs/langgraph/langgraph/channels/binop.py +++ b/libs/langgraph/langgraph/channels/binop.py @@ -1,5 +1,6 @@ import collections.abc -from typing import Callable, Generic, Sequence, Type +from collections.abc import Sequence +from typing import Callable, Generic from typing_extensions import NotRequired, Required, Self @@ -31,7 +32,7 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]): __slots__ = ("value", "operator") - def __init__(self, typ: Type[Value], operator: Callable[[Value, Value], Value]): + def __init__(self, typ: type[Value], operator: Callable[[Value, Value], Value]): super().__init__(typ) self.operator = operator # special forms from typing or collections.abc are not instantiable @@ -57,12 +58,12 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]): ) @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/dynamic_barrier_value.py b/libs/langgraph/langgraph/channels/dynamic_barrier_value.py index 4f75f2a8c..ea6ca9815 100644 --- a/libs/langgraph/langgraph/channels/dynamic_barrier_value.py +++ b/libs/langgraph/langgraph/channels/dynamic_barrier_value.py @@ -1,4 +1,5 @@ -from typing import Any, Generic, NamedTuple, Optional, Sequence, Type, Union +from collections.abc import Sequence +from typing import Any, Generic, NamedTuple, Optional, Union from typing_extensions import Self @@ -28,7 +29,7 @@ class DynamicBarrierValue( names: Optional[set[Value]] seen: set[Value] - def __init__(self, typ: Type[Value]) -> None: + def __init__(self, typ: type[Value]) -> None: super().__init__(typ) self.names = None self.seen = set() @@ -37,12 +38,12 @@ class DynamicBarrierValue( return isinstance(value, DynamicBarrierValue) and value.names == self.names @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/ephemeral_value.py b/libs/langgraph/langgraph/channels/ephemeral_value.py index e2beaf05d..7448be106 100644 --- a/libs/langgraph/langgraph/channels/ephemeral_value.py +++ b/libs/langgraph/langgraph/channels/ephemeral_value.py @@ -1,4 +1,5 @@ -from typing import Any, Generic, Sequence, Type +from collections.abc import Sequence +from typing import Any, Generic from typing_extensions import Self @@ -21,12 +22,12 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]): return isinstance(value, EphemeralValue) and value.guard == self.guard @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/last_value.py b/libs/langgraph/langgraph/channels/last_value.py index 7232b8f65..c067aeb00 100644 --- a/libs/langgraph/langgraph/channels/last_value.py +++ b/libs/langgraph/langgraph/channels/last_value.py @@ -1,4 +1,5 @@ -from typing import Any, Generic, Sequence, Type +from collections.abc import Sequence +from typing import Any, Generic from typing_extensions import Self @@ -25,12 +26,12 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]): return isinstance(value, LastValue) @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/named_barrier_value.py b/libs/langgraph/langgraph/channels/named_barrier_value.py index 1d4b32097..628c0bcf2 100644 --- a/libs/langgraph/langgraph/channels/named_barrier_value.py +++ b/libs/langgraph/langgraph/channels/named_barrier_value.py @@ -1,4 +1,5 @@ -from typing import Generic, Sequence, Type +from collections.abc import Sequence +from typing import Generic from typing_extensions import Self @@ -15,7 +16,7 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]): names: set[Value] seen: set[Value] - def __init__(self, typ: Type[Value], names: set[Value]) -> None: + def __init__(self, typ: type[Value], names: set[Value]) -> None: super().__init__(typ) self.names = names self.seen: set[str] = set() @@ -24,12 +25,12 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]): return isinstance(value, NamedBarrierValue) and value.names == self.names @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/channels/topic.py b/libs/langgraph/langgraph/channels/topic.py index 42c665bb5..4cb320ee0 100644 --- a/libs/langgraph/langgraph/channels/topic.py +++ b/libs/langgraph/langgraph/channels/topic.py @@ -1,4 +1,5 @@ -from typing import Any, Generic, Iterator, Sequence, Type, Union +from collections.abc import Iterator, Sequence +from typing import Any, Generic, Union from typing_extensions import Self @@ -28,7 +29,7 @@ class Topic( __slots__ = ("values", "accumulate") - def __init__(self, typ: Type[Value], accumulate: bool = False) -> None: + def __init__(self, typ: type[Value], accumulate: bool = False) -> None: super().__init__(typ) # attrs self.accumulate = accumulate diff --git a/libs/langgraph/langgraph/channels/untracked_value.py b/libs/langgraph/langgraph/channels/untracked_value.py index 035beac02..e0c9cb676 100644 --- a/libs/langgraph/langgraph/channels/untracked_value.py +++ b/libs/langgraph/langgraph/channels/untracked_value.py @@ -1,4 +1,5 @@ -from typing import Generic, Sequence, Type +from collections.abc import Sequence +from typing import Generic from typing_extensions import Self @@ -12,7 +13,7 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]): __slots__ = ("value", "guard") - def __init__(self, typ: Type[Value], guard: bool = True) -> None: + def __init__(self, typ: type[Value], guard: bool = True) -> None: super().__init__(typ) self.guard = guard self.value = MISSING @@ -21,12 +22,12 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]): return isinstance(value, UntrackedValue) and value.guard == self.guard @property - def ValueType(self) -> Type[Value]: + def ValueType(self) -> type[Value]: """The type of the value stored in the channel.""" return self.typ @property - def UpdateType(self) -> Type[Value]: + def UpdateType(self) -> type[Value]: """The type of the update received by the channel.""" return self.typ diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index 4c26324b7..0ebab0a6f 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -1,6 +1,7 @@ import sys +from collections.abc import Mapping from types import MappingProxyType -from typing import Any, Literal, Mapping, cast +from typing import Any, Literal, cast from langgraph.types import Interrupt, Send # noqa: F401 diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 8e78a8784..09e5052e2 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -1,5 +1,6 @@ +from collections.abc import Sequence from enum import Enum -from typing import Any, Sequence +from typing import Any from langgraph.checkpoint.base import EmptyChannelError # noqa: F401 from langgraph.types import Command, Interrupt diff --git a/libs/langgraph/langgraph/func/__init__.py b/libs/langgraph/langgraph/func/__init__.py index 92d8419d9..2a1f8fd6a 100644 --- a/libs/langgraph/langgraph/func/__init__.py +++ b/libs/langgraph/langgraph/func/__init__.py @@ -2,14 +2,13 @@ import asyncio import concurrent.futures import functools import inspect +from collections.abc import Awaitable, Sequence from dataclasses import dataclass from typing import ( Any, - Awaitable, Callable, Generic, Optional, - Sequence, TypeVar, Union, get_args, diff --git a/libs/langgraph/langgraph/graph/branch.py b/libs/langgraph/langgraph/graph/branch.py index 33a2aca1e..a4aa9ede5 100644 --- a/libs/langgraph/langgraph/graph/branch.py +++ b/libs/langgraph/langgraph/graph/branch.py @@ -1,3 +1,4 @@ +from collections.abc import Awaitable, Hashable, Sequence from inspect import ( isfunction, ismethod, @@ -6,14 +7,10 @@ from inspect import ( from types import FunctionType from typing import ( Any, - Awaitable, Callable, - Hashable, Literal, NamedTuple, Optional, - Sequence, - Type, Union, cast, get_args, @@ -42,7 +39,7 @@ def _get_branch_path_input_schema( Callable[..., Awaitable[Union[Hashable, list[Hashable]]]], Runnable[Any, Union[Hashable, list[Hashable]]], ], -) -> Optional[Type[Any]]: +) -> Optional[type[Any]]: input = None # detect input schema annotation in the branch callable try: @@ -85,7 +82,7 @@ class Branch(NamedTuple): path: Runnable[Any, Union[Hashable, list[Hashable]]] ends: Optional[dict[Hashable, str]] then: Optional[str] = None - input_schema: Optional[Type[Any]] = None + input_schema: Optional[type[Any]] = None @classmethod def from_path( diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index fa28243fb..5f40c3eed 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -1,14 +1,12 @@ import asyncio import logging from collections import defaultdict +from collections.abc import Awaitable, Hashable, Sequence from typing import ( Any, - Awaitable, Callable, - Hashable, NamedTuple, Optional, - Sequence, Union, cast, overload, @@ -182,7 +180,7 @@ class Graph: # validate the condition if name in self.branches[source]: raise ValueError( - f"Branch with name `{path.name}` already exists for node " f"`{source}`" + f"Branch with name `{path.name}` already exists for node `{source}`" ) # save it self.branches[source][name] = Branch.from_path(path, path_map, then, False) diff --git a/libs/langgraph/langgraph/graph/message.py b/libs/langgraph/langgraph/graph/message.py index 2892051c5..0935c95e0 100644 --- a/libs/langgraph/langgraph/graph/message.py +++ b/libs/langgraph/langgraph/graph/message.py @@ -1,5 +1,6 @@ import uuid import warnings +from collections.abc import Sequence from functools import partial from typing import ( Annotated, @@ -7,7 +8,6 @@ from typing import ( Callable, Literal, Optional, - Sequence, Union, cast, ) diff --git a/libs/langgraph/langgraph/graph/schema_utils.py b/libs/langgraph/langgraph/graph/schema_utils.py index c1e6eae5e..83b5a1b58 100644 --- a/libs/langgraph/langgraph/graph/schema_utils.py +++ b/libs/langgraph/langgraph/graph/schema_utils.py @@ -3,10 +3,10 @@ import logging import weakref from inspect import isclass from typing import ( + Annotated, Any, Callable, Optional, - Type, Union, get_args, get_origin, @@ -15,14 +15,13 @@ from typing import ( from pydantic import BaseModel from pydantic.v1 import BaseModel as BaseModelV1 -from typing_extensions import Annotated __all__ = ["SchemaCoercionMapper"] logger = logging.getLogger(__name__) -_cache: weakref.WeakKeyDictionary[Type[Any], dict[int, "SchemaCoercionMapper"]] = ( +_cache: weakref.WeakKeyDictionary[type[Any], dict[int, "SchemaCoercionMapper"]] = ( weakref.WeakKeyDictionary() ) @@ -32,7 +31,7 @@ class SchemaCoercionMapper: def __new__( cls, - schema: Type[Any], + schema: type[Any], type_hints: Optional[dict[str, Any]] = None, *, max_depth: int = 12, @@ -46,7 +45,7 @@ class SchemaCoercionMapper: def __init__( self, - schema: Type[Any], + schema: type[Any], type_hints: Optional[dict[str, Any]] = None, *, max_depth: int = 12, @@ -187,7 +186,7 @@ class SchemaCoercionMapper: def dict_coercer(v: Any, d: Any) -> Any: if not isinstance(v, dict): if throw: - raise TypeError("Expected dict, got %s" % type(v)) + raise TypeError(f"Expected dict, got {type(v)}") return v return dict_coercer @@ -197,7 +196,7 @@ class SchemaCoercionMapper: def dict_coercer(v: Any, d: Any) -> Any: if not isinstance(v, dict): if throw: - raise TypeError("Expected dict, got %s" % type(v)) + raise TypeError(f"Expected dict, got {type(v)}") return v return {k_sub(k, d - 1): v_sub(val, d - 1) for k, val in v.items()} diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index fed12fd8b..7d207f089 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -3,19 +3,16 @@ import logging import typing import warnings from collections import defaultdict +from collections.abc import Awaitable, Hashable, Sequence from functools import partial from inspect import isclass, isfunction, ismethod, signature from types import FunctionType from typing import ( Any, - Awaitable, Callable, - Hashable, Literal, NamedTuple, Optional, - Sequence, - Type, Union, cast, get_args, @@ -84,7 +81,7 @@ from langgraph.utils.runnable import RunnableLike, coerce_to_runnable logger = logging.getLogger(__name__) -def _warn_invalid_state_schema(schema: Union[Type[Any], Any]) -> None: +def _warn_invalid_state_schema(schema: Union[type[Any], Any]) -> None: if isinstance(schema, type): return if typing.get_args(schema): @@ -108,7 +105,7 @@ def _get_node_name(node: RunnableLike) -> str: class StateNodeSpec(NamedTuple): runnable: Runnable metadata: Optional[dict[str, Any]] - input: Type[Any] + input: type[Any] retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] ends: Optional[Union[tuple[str, ...], dict[str, str]]] = EMPTY_SEQ @@ -166,15 +163,15 @@ class StateGraph(Graph): nodes: dict[str, StateNodeSpec] # type: ignore[assignment] channels: dict[str, BaseChannel] managed: dict[str, ManagedValueSpec] - schemas: dict[Type[Any], dict[str, Union[BaseChannel, ManagedValueSpec]]] + schemas: dict[type[Any], dict[str, Union[BaseChannel, ManagedValueSpec]]] def __init__( self, - state_schema: Optional[Type[Any]] = None, - config_schema: Optional[Type[Any]] = None, + state_schema: Optional[type[Any]] = None, + config_schema: Optional[type[Any]] = None, *, - input: Optional[Type[Any]] = None, - output: Optional[Type[Any]] = None, + input: Optional[type[Any]] = None, + output: Optional[type[Any]] = None, ) -> None: super().__init__() if state_schema is None: @@ -195,7 +192,7 @@ class StateGraph(Graph): self.schemas = {} self.channels = {} self.managed = {} - self.type_hints: dict[Type[Any], dict[str, Any]] = {} + self.type_hints: dict[type[Any], dict[str, Any]] = {} self.schema = state_schema self.input = input self.output = output @@ -211,7 +208,7 @@ class StateGraph(Graph): (start, end) for starts, end in self.waiting_edges for start in starts } - def _add_schema(self, schema: Type[Any], /, allow_managed: bool = True) -> None: + def _add_schema(self, schema: type[Any], /, allow_managed: bool = True) -> None: if schema not in self.schemas: _warn_invalid_state_schema(schema) channels, managed, type_hints = _get_channels(schema) @@ -250,7 +247,7 @@ class StateGraph(Graph): node: RunnableLike, *, metadata: Optional[dict[str, Any]] = None, - input: Optional[Type[Any]] = None, + input: Optional[type[Any]] = None, retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None, destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None, ) -> Self: @@ -275,7 +272,7 @@ class StateGraph(Graph): action: RunnableLike, *, metadata: Optional[dict[str, Any]] = None, - input: Optional[Type[Any]] = None, + input: Optional[type[Any]] = None, retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None, destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None, ) -> Self: @@ -299,7 +296,7 @@ class StateGraph(Graph): action: Optional[RunnableLike] = None, *, metadata: Optional[dict[str, Any]] = None, - input: Optional[Type[Any]] = None, + input: Optional[type[Any]] = None, retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None, destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None, ) -> Self: @@ -527,7 +524,7 @@ class StateGraph(Graph): # validate the condition if name in self.branches[source]: raise ValueError( - f"Branch with name `{path.name}` already exists for node " f"`{source}`" + f"Branch with name `{path.name}` already exists for node `{source}`" ) # save it self.branches[source][name] = Branch.from_path(path, path_map, then, True) @@ -686,12 +683,12 @@ class StateGraph(Graph): class CompiledStateGraph(CompiledGraph): builder: StateGraph - schema_to_mapper: dict[Type[Any], Optional[Callable[[Any], Any]]] + schema_to_mapper: dict[type[Any], Optional[Callable[[Any], Any]]] def __init__( self, *, - schema_to_mapper: dict[Type[Any], Optional[Callable[[Any], Any]]], + schema_to_mapper: dict[type[Any], Optional[Callable[[Any], Any]]], **kwargs: Any, ) -> None: super().__init__(**kwargs) @@ -1015,7 +1012,7 @@ class CompiledStateGraph(CompiledGraph): def _pick_mapper( - state_keys: Sequence[str], schema: Type[Any], type_hints: Optional[dict[str, Any]] + state_keys: Sequence[str], schema: type[Any], type_hints: Optional[dict[str, Any]] ) -> Optional[Callable[[Any], Any]]: if state_keys == ["__root__"]: return None @@ -1027,7 +1024,7 @@ def _pick_mapper( return partial(_coerce_state, schema) -def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]: +def _coerce_state(schema: type[Any], input: dict[str, Any]) -> dict[str, Any]: return schema(**input) @@ -1083,7 +1080,7 @@ def _get_root(input: Any) -> Optional[Sequence[tuple[str, Any]]]: def _get_channels( - schema: Type[dict], + schema: type[dict], ) -> tuple[dict[str, BaseChannel], dict[str, ManagedValueSpec], dict[str, Any]]: if not hasattr(schema, "__annotations__"): return ( @@ -1137,7 +1134,7 @@ def _get_channel( return fallback -def _is_field_channel(typ: Type[Any]) -> Optional[BaseChannel]: +def _is_field_channel(typ: type[Any]) -> Optional[BaseChannel]: if hasattr(typ, "__metadata__"): meta = typ.__metadata__ if len(meta) >= 1 and isinstance(meta[-1], BaseChannel): @@ -1147,7 +1144,7 @@ def _is_field_channel(typ: Type[Any]) -> Optional[BaseChannel]: return None -def _is_field_binop(typ: Type[Any]) -> Optional[BinaryOperatorAggregate]: +def _is_field_binop(typ: type[Any]) -> Optional[BinaryOperatorAggregate]: if hasattr(typ, "__metadata__"): meta = typ.__metadata__ if len(meta) >= 1 and callable(meta[-1]): @@ -1168,7 +1165,7 @@ def _is_field_binop(typ: Type[Any]) -> Optional[BinaryOperatorAggregate]: return None -def _is_field_managed_value(name: str, typ: Type[Any]) -> Optional[ManagedValueSpec]: +def _is_field_managed_value(name: str, typ: type[Any]) -> Optional[ManagedValueSpec]: if hasattr(typ, "__metadata__"): meta = typ.__metadata__ if len(meta) >= 1: @@ -1186,7 +1183,7 @@ def _is_field_managed_value(name: str, typ: Type[Any]) -> Optional[ManagedValueS def _get_schema( - typ: Type, + typ: type, schemas: dict, channels: dict, name: str, diff --git a/libs/langgraph/langgraph/managed/base.py b/libs/langgraph/langgraph/managed/base.py index 36962e156..a3f305fbe 100644 --- a/libs/langgraph/langgraph/managed/base.py +++ b/libs/langgraph/langgraph/managed/base.py @@ -1,14 +1,11 @@ from abc import ABC, abstractmethod +from collections.abc import AsyncIterator, Iterator, Sequence from contextlib import asynccontextmanager, contextmanager from inspect import isclass from typing import ( Any, - AsyncIterator, Generic, - Iterator, NamedTuple, - Sequence, - Type, TypeVar, Union, ) @@ -66,11 +63,11 @@ class WritableManagedValue(Generic[V, U], ManagedValue[V], ABC): class ConfiguredManagedValue(NamedTuple): - cls: Type[ManagedValue] + cls: type[ManagedValue] kwargs: dict[str, Any] -ManagedValueSpec = Union[Type[ManagedValue], ConfiguredManagedValue] +ManagedValueSpec = Union[type[ManagedValue], ConfiguredManagedValue] def is_managed_value(value: Any) -> TypeGuard[ManagedValueSpec]: @@ -79,7 +76,7 @@ def is_managed_value(value: Any) -> TypeGuard[ManagedValueSpec]: ) -def is_readonly_managed_value(value: Any) -> TypeGuard[Type[ManagedValue]]: +def is_readonly_managed_value(value: Any) -> TypeGuard[type[ManagedValue]]: return ( isclass(value) and issubclass(value, ManagedValue) @@ -90,7 +87,7 @@ def is_readonly_managed_value(value: Any) -> TypeGuard[Type[ManagedValue]]: ) -def is_writable_managed_value(value: Any) -> TypeGuard[Type[WritableManagedValue]]: +def is_writable_managed_value(value: Any) -> TypeGuard[type[WritableManagedValue]]: return (isclass(value) and issubclass(value, WritableManagedValue)) or ( isinstance(value, ConfiguredManagedValue) and issubclass(value.cls, WritableManagedValue) diff --git a/libs/langgraph/langgraph/managed/context.py b/libs/langgraph/langgraph/managed/context.py index d1713c11a..1352254d5 100644 --- a/libs/langgraph/langgraph/managed/context.py +++ b/libs/langgraph/langgraph/managed/context.py @@ -1,15 +1,16 @@ -from contextlib import asynccontextmanager, contextmanager +from collections.abc import AsyncIterator, Iterator +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + asynccontextmanager, + contextmanager, +) from inspect import signature from typing import ( Any, - AsyncContextManager, - AsyncIterator, Callable, - ContextManager, Generic, - Iterator, Optional, - Type, Union, ) @@ -28,15 +29,15 @@ class Context(ManagedValue[V], Generic[V]): def of( ctx: Union[ None, - Callable[..., ContextManager[V]], - Type[ContextManager[V]], - Callable[..., AsyncContextManager[V]], - Type[AsyncContextManager[V]], + Callable[..., AbstractContextManager[V]], + type[AbstractContextManager[V]], + Callable[..., AbstractAsyncContextManager[V]], + type[AbstractAsyncContextManager[V]], ] = None, actx: Optional[ Union[ - Callable[..., AsyncContextManager[V]], - Type[AsyncContextManager[V]], + Callable[..., AbstractAsyncContextManager[V]], + type[AbstractAsyncContextManager[V]], ] ] = None, ) -> ConfiguredManagedValue: @@ -98,8 +99,10 @@ class Context(ManagedValue[V], Generic[V]): self, loop: LoopProtocol, *, - ctx: Union[None, Type[ContextManager[V]], Type[AsyncContextManager[V]]] = None, - actx: Optional[Type[AsyncContextManager[V]]] = None, + ctx: Union[ + None, type[AbstractContextManager[V]], type[AbstractAsyncContextManager[V]] + ] = None, + actx: Optional[type[AbstractAsyncContextManager[V]]] = None, ) -> None: self.ctx = ctx self.actx = actx diff --git a/libs/langgraph/langgraph/managed/shared_value.py b/libs/langgraph/langgraph/managed/shared_value.py index 300d36c7d..39f4684cc 100644 --- a/libs/langgraph/langgraph/managed/shared_value.py +++ b/libs/langgraph/langgraph/managed/shared_value.py @@ -1,12 +1,9 @@ import collections.abc +from collections.abc import AsyncIterator, Iterator, Sequence from contextlib import asynccontextmanager, contextmanager from typing import ( Any, - AsyncIterator, - Iterator, Optional, - Sequence, - Type, ) from typing_extensions import NotRequired, Required, Self @@ -71,7 +68,7 @@ class SharedValue(WritableManagedValue[Value, Update]): yield value def __init__( - self, loop: LoopProtocol, *, typ: Type[Any], scope: str, key: str + self, loop: LoopProtocol, *, typ: type[Any], scope: str, key: str ) -> None: super().__init__(loop) if typ := _strip_extras(typ): diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 2b016e587..266ba5df6 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -6,17 +6,11 @@ import concurrent.futures import queue import weakref from collections import defaultdict, deque +from collections.abc import AsyncIterator, Iterator, Mapping, Sequence from functools import partial from typing import ( Any, - AsyncIterator, Callable, - Dict, - Iterator, - Mapping, - Optional, - Sequence, - Type, Union, cast, get_type_hints, @@ -141,8 +135,8 @@ class Channel: cls, channels: str, *, - key: Optional[str] = None, - tags: Optional[list[str]] = None, + key: str | None = None, + tags: list[str] | None = None, ) -> PregelNode: ... @overload @@ -152,16 +146,16 @@ class Channel: channels: Sequence[str], *, key: None = None, - tags: Optional[list[str]] = None, + tags: list[str] | None = None, ) -> PregelNode: ... @classmethod def subscribe_to( cls, - channels: Union[str, Sequence[str]], + channels: str | Sequence[str], *, - key: Optional[str] = None, - tags: Optional[list[str]] = None, + key: str | None = None, + tags: list[str] | None = None, ) -> PregelNode: """Runs process.invoke() each time channels are updated, with a dict of the channel values as input.""" @@ -467,7 +461,7 @@ class Pregel(PregelProtocol): nodes: dict[str, PregelNode] - channels: dict[str, Union[BaseChannel, ManagedValueSpec]] + channels: dict[str, BaseChannel | ManagedValueSpec] stream_mode: StreamMode = "values" """Mode to stream output, defaults to 'values'.""" @@ -476,18 +470,18 @@ class Pregel(PregelProtocol): """Whether to force emitting stream events eagerly, automatically turned on for stream_mode "messages" and "custom".""" - output_channels: Union[str, Sequence[str]] + output_channels: str | Sequence[str] - stream_channels: Optional[Union[str, Sequence[str]]] = None + stream_channels: str | Sequence[str] | None = None """Channels to stream, defaults to all channels not in reserved channels""" - interrupt_after_nodes: Union[All, Sequence[str]] + interrupt_after_nodes: All | Sequence[str] - interrupt_before_nodes: Union[All, Sequence[str]] + interrupt_before_nodes: All | Sequence[str] - input_channels: Union[str, Sequence[str]] + input_channels: str | Sequence[str] - step_timeout: Optional[float] = None + step_timeout: float | None = None """Maximum time to wait for a step to complete, in seconds. Defaults to None.""" debug: bool @@ -496,44 +490,44 @@ class Pregel(PregelProtocol): checkpointer: Checkpointer = None """Checkpointer used to save and load graph state. Defaults to None.""" - store: Optional[BaseStore] = None + store: BaseStore | None = None """Memory store to use for SharedValues. Defaults to None.""" - retry_policy: Optional[Sequence[RetryPolicy]] = None + retry_policy: Sequence[RetryPolicy] | None = None """Retry policies to use when running tasks. Set to None to disable.""" - config_type: Optional[Type[Any]] = None + config_type: type[Any] | None = None - input_model: Optional[Type[BaseModel]] = None + input_model: type[BaseModel] | None = None - config: Optional[RunnableConfig] = None + config: RunnableConfig | None = None name: str = "LangGraph" - trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None + trigger_to_nodes: Mapping[str, Sequence[str]] | None = None def __init__( self, *, nodes: dict[str, PregelNode], - channels: Optional[dict[str, Union[BaseChannel, ManagedValueSpec]]], + channels: dict[str, BaseChannel | ManagedValueSpec] | None, auto_validate: bool = True, stream_mode: StreamMode = "values", stream_eager: bool = False, - output_channels: Union[str, Sequence[str]], - stream_channels: Optional[Union[str, Sequence[str]]] = None, - interrupt_after_nodes: Union[All, Sequence[str]] = (), - interrupt_before_nodes: Union[All, Sequence[str]] = (), - input_channels: Union[str, Sequence[str]], - step_timeout: Optional[float] = None, - debug: Optional[bool] = None, - checkpointer: Optional[BaseCheckpointSaver] = None, - store: Optional[BaseStore] = None, - retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None, - config_type: Optional[Type[Any]] = None, - input_model: Optional[Type[BaseModel]] = None, - config: Optional[RunnableConfig] = None, - trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None, + output_channels: str | Sequence[str], + stream_channels: str | Sequence[str] | None = None, + interrupt_after_nodes: All | Sequence[str] = (), + interrupt_before_nodes: All | Sequence[str] = (), + input_channels: str | Sequence[str], + step_timeout: float | None = None, + debug: bool | None = None, + checkpointer: BaseCheckpointSaver | None = None, + store: BaseStore | None = None, + retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None, + config_type: type[Any] | None = None, + input_model: type[BaseModel] | None = None, + config: RunnableConfig | None = None, + trigger_to_nodes: Mapping[str, Sequence[str]] | None = None, name: str = "LangGraph", ) -> None: self.nodes = nodes @@ -562,22 +556,20 @@ class Pregel(PregelProtocol): self.validate() def get_graph( - self, config: Optional[RunnableConfig] = None, *, xray: Union[int, bool] = False + self, config: RunnableConfig | None = None, *, xray: int | bool = False ) -> Graph: raise NotImplementedError async def aget_graph( - self, config: Optional[RunnableConfig] = None, *, xray: Union[int, bool] = False + self, config: RunnableConfig | None = None, *, xray: int | bool = False ) -> Graph: raise NotImplementedError - def copy(self, update: Optional[dict[str, Any]] = None) -> Self: + def copy(self, update: dict[str, Any] | None = None) -> Self: attrs = {**self.__dict__, **(update or {})} return self.__class__(**attrs) - def with_config( - self, config: Optional[RunnableConfig] = None, **kwargs: Any - ) -> Self: + def with_config(self, config: RunnableConfig | None = None, **kwargs: Any) -> Self: return self.copy( {"config": merge_configs(self.config, config, cast(RunnableConfig, kwargs))} ) @@ -632,9 +624,7 @@ class Pregel(PregelProtocol): ] ] - def config_schema( - self, *, include: Optional[Sequence[str]] = None - ) -> Type[BaseModel]: + def config_schema(self, *, include: Sequence[str] | None = None) -> type[BaseModel]: # If the config type is not set explicitly, we will try to infer it. # If the config type is provided, but isn't directly supported by pydantic # (e.g., vanilla python class), we will also delegate to the parent class, @@ -654,8 +644,8 @@ class Pregel(PregelProtocol): return create_model(self.get_name("Config"), field_definitions=fields) def get_config_jsonschema( - self, *, include: Optional[Sequence[str]] = None - ) -> Dict[str, Any]: + self, *, include: Sequence[str] | None = None + ) -> dict[str, Any]: schema = self.config_schema(include=include) if hasattr(schema, "model_json_schema"): return schema.model_json_schema() @@ -669,9 +659,7 @@ class Pregel(PregelProtocol): if isinstance(channel, BaseChannel): return channel.UpdateType - def get_input_schema( - self, config: Optional[RunnableConfig] = None - ) -> Type[BaseModel]: + def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]: if self.input_model is not None: return self.input_model config = merge_configs(self.config, config) @@ -688,8 +676,8 @@ class Pregel(PregelProtocol): ) def get_input_jsonschema( - self, config: Optional[RunnableConfig] = None - ) -> Dict[str, Any]: + self, config: RunnableConfig | None = None + ) -> dict[str, Any]: schema = self.get_input_schema(config) if hasattr(schema, "model_json_schema"): return schema.model_json_schema() @@ -704,8 +692,8 @@ class Pregel(PregelProtocol): return channel.ValueType def get_output_schema( - self, config: Optional[RunnableConfig] = None - ) -> Type[BaseModel]: + self, config: RunnableConfig | None = None + ) -> type[BaseModel]: config = merge_configs(self.config, config) if isinstance(self.output_channels, str): return super().get_output_schema(config) @@ -720,8 +708,8 @@ class Pregel(PregelProtocol): ) def get_output_jsonschema( - self, config: Optional[RunnableConfig] = None - ) -> Dict[str, Any]: + self, config: RunnableConfig | None = None + ) -> dict[str, Any]: schema = self.get_output_schema(config) if hasattr(schema, "model_json_schema"): return schema.model_json_schema() @@ -736,13 +724,13 @@ class Pregel(PregelProtocol): ) @property - def stream_channels_asis(self) -> Union[str, Sequence[str]]: + def stream_channels_asis(self) -> str | Sequence[str]: return self.stream_channels or [ k for k in self.channels if isinstance(self.channels[k], BaseChannel) ] def get_subgraphs( - self, *, namespace: Optional[str] = None, recurse: bool = False + self, *, namespace: str | None = None, recurse: bool = False ) -> Iterator[tuple[str, PregelProtocol]]: for name, node in self.nodes.items(): # filter by prefix @@ -771,7 +759,7 @@ class Pregel(PregelProtocol): ) async def aget_subgraphs( - self, *, namespace: Optional[str] = None, recurse: bool = False + self, *, namespace: str | None = None, recurse: bool = False ) -> AsyncIterator[tuple[str, PregelProtocol]]: for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse): yield name, node @@ -783,8 +771,8 @@ class Pregel(PregelProtocol): def _prepare_state_snapshot( self, config: RunnableConfig, - saved: Optional[CheckpointTuple], - recurse: Optional[BaseCheckpointSaver] = None, + saved: CheckpointTuple | None, + recurse: BaseCheckpointSaver | None = None, apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: @@ -832,7 +820,7 @@ class Pregel(PregelProtocol): # get the subgraphs subgraphs = dict(self.get_subgraphs()) parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {} + task_states: dict[str, RunnableConfig | StateSnapshot] = {} for task in next_tasks.values(): if task.name not in subgraphs: continue @@ -899,8 +887,8 @@ class Pregel(PregelProtocol): async def _aprepare_state_snapshot( self, config: RunnableConfig, - saved: Optional[CheckpointTuple], - recurse: Optional[BaseCheckpointSaver] = None, + saved: CheckpointTuple | None, + recurse: BaseCheckpointSaver | None = None, apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: @@ -951,7 +939,7 @@ class Pregel(PregelProtocol): # get the subgraphs subgraphs = {n: g async for n, g in self.aget_subgraphs()} parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "") - task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {} + task_states: dict[str, RunnableConfig | StateSnapshot] = {} for task in next_tasks.values(): if task.name not in subgraphs: continue @@ -1019,7 +1007,7 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: """Get the current state of the graph.""" - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1061,7 +1049,7 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: """Get the current state of the graph.""" - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1103,13 +1091,13 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, *, - filter: Optional[Dict[str, Any]] = None, - before: Optional[RunnableConfig] = None, - limit: Optional[int] = None, + filter: dict[str, Any] | None = None, + before: RunnableConfig | None = None, + limit: int | None = None, ) -> Iterator[StateSnapshot]: config = ensure_config(config) """Get the history of the state of the graph.""" - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1154,13 +1142,13 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, *, - filter: Optional[Dict[str, Any]] = None, - before: Optional[RunnableConfig] = None, - limit: Optional[int] = None, + filter: dict[str, Any] | None = None, + before: RunnableConfig | None = None, + limit: int | None = None, ) -> AsyncIterator[StateSnapshot]: config = ensure_config(config) """Get the history of the state of the graph.""" - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1225,7 +1213,7 @@ class Pregel(PregelProtocol): RunnableConfig: The updated config. """ - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1500,7 +1488,7 @@ class Pregel(PregelProtocol): next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(checkpoint, channels, tasks, None) - valid_updates: list[tuple[str, Optional[dict[str, Any]]]] = [] + valid_updates: list[tuple[str, dict[str, Any] | None]] = [] if len(updates) == 1: values, as_node = updates[0] # find last node that updated the state, if not provided @@ -1639,7 +1627,7 @@ class Pregel(PregelProtocol): RunnableConfig: The updated config. """ - checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get( + checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get( CONFIG_KEY_CHECKPOINTER, self.checkpointer ) if not checkpointer: @@ -1914,7 +1902,7 @@ class Pregel(PregelProtocol): next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(checkpoint, channels, tasks, None) - valid_updates: list[tuple[str, Optional[dict[str, Any]]]] = [] + valid_updates: list[tuple[str, dict[str, Any] | None]] = [] if len(updates) == 1: values, as_node = updates[0] # find last node that updated the state, if not provided @@ -2034,8 +2022,8 @@ class Pregel(PregelProtocol): def update_state( self, config: RunnableConfig, - values: Optional[Union[dict[str, Any], Any]], - as_node: Optional[str] = None, + values: dict[str, Any] | Any | None, + as_node: str | None = None, ) -> RunnableConfig: """Update the state of the graph with the given values, as if they came from node `as_node`. If `as_node` is not provided, it will be set to the last node @@ -2047,7 +2035,7 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, values: dict[str, Any] | Any, - as_node: Optional[str] = None, + as_node: str | None = None, ) -> RunnableConfig: """Update the state of the graph asynchronously with the given values, as if they came from node `as_node`. If `as_node` is not provided, it will be set to the last node @@ -2059,19 +2047,19 @@ class Pregel(PregelProtocol): self, config: RunnableConfig, *, - stream_mode: Optional[Union[StreamMode, list[StreamMode]]], - output_keys: Optional[Union[str, Sequence[str]]], - interrupt_before: Optional[Union[All, Sequence[str]]], - interrupt_after: Optional[Union[All, Sequence[str]]], - debug: Optional[bool], + stream_mode: StreamMode | list[StreamMode] | None, + output_keys: str | Sequence[str] | None, + interrupt_before: All | Sequence[str] | None, + interrupt_after: All | Sequence[str] | None, + debug: bool | None, ) -> tuple[ bool, set[StreamMode], - Union[str, Sequence[str]], - Union[All, Sequence[str]], - Union[All, Sequence[str]], - Optional[BaseCheckpointSaver], - Optional[BaseStore], + str | Sequence[str], + All | Sequence[str], + All | Sequence[str], + BaseCheckpointSaver | None, + BaseStore | None, ]: if config["recursion_limit"] < 1: raise ValueError("recursion_limit must be at least 1") @@ -2089,7 +2077,7 @@ class Pregel(PregelProtocol): # if being called as a node in another graph, always use values mode stream_mode = ["values"] if self.checkpointer is False: - checkpointer: Optional[BaseCheckpointSaver] = None + checkpointer: BaseCheckpointSaver | None = None elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}): checkpointer = config[CONF][CONFIG_KEY_CHECKPOINTER] elif self.checkpointer is True: @@ -2101,7 +2089,7 @@ class Pregel(PregelProtocol): f"Checkpointer requires one or more of the following 'configurable' keys: {[s.id for s in checkpointer.config_specs]}" ) if CONFIG_KEY_STORE in config.get(CONF, {}): - store: Optional[BaseStore] = config[CONF][CONFIG_KEY_STORE] + store: BaseStore | None = config[CONF][CONFIG_KEY_STORE] else: store = self.store return ( @@ -2116,17 +2104,17 @@ class Pregel(PregelProtocol): def stream( self, - input: Union[dict[str, Any], Any], - config: Optional[RunnableConfig] = None, + input: dict[str, Any] | Any, + config: RunnableConfig | None = None, *, - stream_mode: Optional[Union[StreamMode, list[StreamMode]]] = None, - output_keys: Optional[Union[str, Sequence[str]]] = None, - interrupt_before: Optional[Union[All, Sequence[str]]] = None, - interrupt_after: Optional[Union[All, Sequence[str]]] = None, - checkpoint_during: Optional[bool] = None, - debug: Optional[bool] = None, + stream_mode: StreamMode | list[StreamMode] | None = None, + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, subgraphs: bool = False, - ) -> Iterator[Union[dict[str, Any], Any]]: + ) -> Iterator[dict[str, Any] | Any]: """Stream graph steps for a single input. Args: @@ -2352,7 +2340,7 @@ class Pregel(PregelProtocol): ): # we are careful to have a single waiter live at any one time # because on exit we increment semaphore count by exactly 1 - waiter: Optional[concurrent.futures.Future] = None + waiter: concurrent.futures.Future | None = None # because sync futures cannot be cancelled, we instead # release the stream semaphore on exit, which will cause # a pending waiter to return immediately @@ -2403,17 +2391,17 @@ class Pregel(PregelProtocol): async def astream( self, - input: Union[dict[str, Any], Any], - config: Optional[RunnableConfig] = None, + input: dict[str, Any] | Any, + config: RunnableConfig | None = None, *, - stream_mode: Optional[Union[StreamMode, list[StreamMode]]] = None, - output_keys: Optional[Union[str, Sequence[str]]] = None, - interrupt_before: Optional[Union[All, Sequence[str]]] = None, - interrupt_after: Optional[Union[All, Sequence[str]]] = None, - checkpoint_during: Optional[bool] = None, - debug: Optional[bool] = None, + stream_mode: StreamMode | list[StreamMode] | None = None, + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, subgraphs: bool = False, - ) -> AsyncIterator[Union[dict[str, Any], Any]]: + ) -> AsyncIterator[dict[str, Any] | Any]: """Stream graph steps for a single input. Args: @@ -2704,17 +2692,17 @@ class Pregel(PregelProtocol): def invoke( self, - input: Union[dict[str, Any], Any], - config: Optional[RunnableConfig] = None, + input: dict[str, Any] | Any, + config: RunnableConfig | None = None, *, stream_mode: StreamMode = "values", - output_keys: Optional[Union[str, Sequence[str]]] = None, - interrupt_before: Optional[Union[All, Sequence[str]]] = None, - interrupt_after: Optional[Union[All, Sequence[str]]] = None, - checkpoint_during: Optional[bool] = None, - debug: Optional[bool] = None, + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, **kwargs: Any, - ) -> Union[dict[str, Any], Any]: + ) -> dict[str, Any] | Any: """Run the graph with a single input and config. Args: @@ -2733,7 +2721,7 @@ class Pregel(PregelProtocol): """ output_keys = output_keys if output_keys is not None else self.output_channels if stream_mode == "values": - latest: Union[dict[str, Any], Any] = None + latest: dict[str, Any] | Any = None else: chunks = [] for chunk in self.stream( @@ -2758,17 +2746,17 @@ class Pregel(PregelProtocol): async def ainvoke( self, - input: Union[dict[str, Any], Any], - config: Optional[RunnableConfig] = None, + input: dict[str, Any] | Any, + config: RunnableConfig | None = None, *, stream_mode: StreamMode = "values", - output_keys: Optional[Union[str, Sequence[str]]] = None, - interrupt_before: Optional[Union[All, Sequence[str]]] = None, - interrupt_after: Optional[Union[All, Sequence[str]]] = None, - checkpoint_during: Optional[bool] = None, - debug: Optional[bool] = None, + output_keys: str | Sequence[str] | None = None, + interrupt_before: All | Sequence[str] | None = None, + interrupt_after: All | Sequence[str] | None = None, + checkpoint_during: bool | None = None, + debug: bool | None = None, **kwargs: Any, - ) -> Union[dict[str, Any], Any]: + ) -> dict[str, Any] | Any: """Asynchronously invoke the graph on a single input. Args: @@ -2788,7 +2776,7 @@ class Pregel(PregelProtocol): output_keys = output_keys if output_keys is not None else self.output_channels if stream_mode == "values": - latest: Union[dict[str, Any], Any] = None + latest: dict[str, Any] | Any = None else: chunks = [] async for chunk in self.astream( diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/algo.py index 68bcc9b82..94a5ade90 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/algo.py @@ -3,19 +3,17 @@ import itertools import sys import threading from collections import defaultdict, deque +from collections.abc import Iterable, Mapping, Sequence from copy import copy from functools import partial from hashlib import sha1 from typing import ( Any, Callable, - Iterable, Literal, - Mapping, NamedTuple, Optional, Protocol, - Sequence, Union, cast, overload, diff --git a/libs/langgraph/langgraph/pregel/call.py b/libs/langgraph/langgraph/pregel/call.py index a16fa36fd..e8dde073a 100644 --- a/libs/langgraph/langgraph/pregel/call.py +++ b/libs/langgraph/langgraph/pregel/call.py @@ -5,7 +5,8 @@ import functools import inspect import sys import types -from typing import Any, Callable, Generator, Generic, Optional, Sequence, TypeVar, cast +from collections.abc import Generator, Sequence +from typing import Any, Callable, Generic, Optional, TypeVar, cast from langchain_core.runnables import Runnable from typing_extensions import ParamSpec @@ -29,16 +30,12 @@ from langgraph.utils.runnable import ( def _getattribute(obj: Any, name: str) -> Any: for subpath in name.split("."): if subpath == "": - raise AttributeError( - "Can't get local attribute {!r} on {!r}".format(name, obj) - ) + raise AttributeError(f"Can't get local attribute {name!r} on {obj!r}") try: parent = obj obj = getattr(obj, subpath) except AttributeError: - raise AttributeError( - "Can't get attribute {!r} on {!r}".format(name, obj) - ) from None + raise AttributeError(f"Can't get attribute {name!r} on {obj!r}") from None return obj, parent diff --git a/libs/langgraph/langgraph/pregel/checkpoint.py b/libs/langgraph/langgraph/pregel/checkpoint.py index b4d96091e..fec604345 100644 --- a/libs/langgraph/langgraph/pregel/checkpoint.py +++ b/libs/langgraph/langgraph/pregel/checkpoint.py @@ -1,5 +1,6 @@ +from collections.abc import Mapping from datetime import datetime, timezone -from typing import Mapping, Optional +from typing import Optional from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import Checkpoint diff --git a/libs/langgraph/langgraph/pregel/debug.py b/libs/langgraph/langgraph/pregel/debug.py index dc2e403a7..b5a213ec4 100644 --- a/libs/langgraph/langgraph/pregel/debug.py +++ b/libs/langgraph/langgraph/pregel/debug.py @@ -1,15 +1,12 @@ from collections import defaultdict +from collections.abc import Iterable, Iterator, Mapping, Sequence from dataclasses import asdict from datetime import datetime, timezone from pprint import pformat from typing import ( Any, - Iterable, - Iterator, Literal, - Mapping, Optional, - Sequence, Union, ) from uuid import UUID diff --git a/libs/langgraph/langgraph/pregel/executor.py b/libs/langgraph/langgraph/pregel/executor.py index 0a117651d..2e53e5adc 100644 --- a/libs/langgraph/langgraph/pregel/executor.py +++ b/libs/langgraph/langgraph/pregel/executor.py @@ -1,15 +1,12 @@ import asyncio import concurrent.futures import time -from contextlib import ExitStack +from collections.abc import Awaitable, Coroutine +from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack from contextvars import copy_context from types import TracebackType from typing import ( - AsyncContextManager, - Awaitable, Callable, - ContextManager, - Coroutine, Optional, Protocol, TypeVar, @@ -40,7 +37,7 @@ class Submit(Protocol[P, T]): ) -> concurrent.futures.Future[T]: ... -class BackgroundExecutor(ContextManager): +class BackgroundExecutor(AbstractContextManager): """A context manager that runs sync tasks in the background. Uses a thread pool executor to delegate tasks to separate threads. On exit, @@ -122,7 +119,7 @@ class BackgroundExecutor(ContextManager): pass -class AsyncBackgroundExecutor(AsyncContextManager): +class AsyncBackgroundExecutor(AbstractAsyncContextManager): """A context manager that runs async tasks in the background. Uses the current event loop to delegate tasks to asyncio tasks. On exit, diff --git a/libs/langgraph/langgraph/pregel/io.py b/libs/langgraph/langgraph/pregel/io.py index 1b9ae78d5..026051a4d 100644 --- a/libs/langgraph/langgraph/pregel/io.py +++ b/libs/langgraph/langgraph/pregel/io.py @@ -1,5 +1,6 @@ from collections import Counter -from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union +from collections.abc import Iterator, Mapping, Sequence +from typing import Any, Literal, Optional, TypeVar, Union from uuid import UUID from langchain_core.runnables.utils import AddableDict diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 644fd5a68..c88362df6 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -3,21 +3,20 @@ import binascii import concurrent.futures import dataclasses from collections import defaultdict, deque -from contextlib import AsyncExitStack, ExitStack +from collections.abc import Iterator, Mapping, Sequence +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + AsyncExitStack, + ExitStack, +) from inspect import signature from types import TracebackType from typing import ( Any, - AsyncContextManager, Callable, - ContextManager, - Iterator, - List, Literal, - Mapping, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -146,7 +145,7 @@ def DuplexStream(*streams: StreamProtocol) -> StreamProtocol: class PregelLoop(LoopProtocol): input: Optional[Any] - input_model: Optional[Type[BaseModel]] + input_model: Optional[type[BaseModel]] checkpointer: Optional[BaseCheckpointSaver] nodes: Mapping[str, PregelNode] specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]] @@ -186,7 +185,7 @@ class PregelLoop(LoopProtocol): checkpoint_ns: tuple[str, ...] checkpoint_config: RunnableConfig checkpoint_metadata: CheckpointMetadata - checkpoint_pending_writes: List[PendingWrite] + checkpoint_pending_writes: list[PendingWrite] checkpoint_previous_versions: dict[str, Union[str, float, int]] prev_checkpoint_config: Optional[RunnableConfig] @@ -214,7 +213,7 @@ class PregelLoop(LoopProtocol): interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ, interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, - input_model: Optional[Type[BaseModel]] = None, + input_model: Optional[type[BaseModel]] = None, debug: bool = False, migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None, trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None, @@ -491,7 +490,7 @@ class PregelLoop(LoopProtocol): if self.input is INPUT_SHOULD_VALIDATE: self.input = INPUT_DONE # validate - cast(Type[BaseModel], self.input_model)( + cast(type[BaseModel], self.input_model)( **read_channels(self.channels, self.stream_keys) ) # produce values output @@ -839,7 +838,7 @@ class PregelLoop(LoopProtocol): def _suppress_interrupt( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> Optional[bool]: @@ -945,7 +944,7 @@ class PregelLoop(LoopProtocol): ) -class SyncPregelLoop(PregelLoop, ContextManager): +class SyncPregelLoop(PregelLoop, AbstractContextManager): def __init__( self, input: Optional[Any], @@ -961,7 +960,7 @@ class SyncPregelLoop(PregelLoop, ContextManager): interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, output_keys: Union[str, Sequence[str]] = EMPTY_SEQ, stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ, - input_model: Optional[Type[BaseModel]] = None, + input_model: Optional[type[BaseModel]] = None, debug: bool = False, migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None, trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None, @@ -1087,7 +1086,7 @@ class SyncPregelLoop(PregelLoop, ContextManager): def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> Optional[bool]: @@ -1095,7 +1094,7 @@ class SyncPregelLoop(PregelLoop, ContextManager): return self.stack.__exit__(exc_type, exc_value, traceback) -class AsyncPregelLoop(PregelLoop, AsyncContextManager): +class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager): def __init__( self, input: Optional[Any], @@ -1111,7 +1110,7 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, output_keys: Union[str, Sequence[str]] = EMPTY_SEQ, stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ, - input_model: Optional[Type[BaseModel]] = None, + input_model: Optional[type[BaseModel]] = None, debug: bool = False, migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None, trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None, @@ -1240,7 +1239,7 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): async def __aexit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> Optional[bool]: diff --git a/libs/langgraph/langgraph/pregel/manager.py b/libs/langgraph/langgraph/pregel/manager.py index b117e830c..2d790720d 100644 --- a/libs/langgraph/langgraph/pregel/manager.py +++ b/libs/langgraph/langgraph/pregel/manager.py @@ -1,6 +1,7 @@ import asyncio +from collections.abc import AsyncIterator, Iterator, Mapping from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager -from typing import AsyncIterator, Iterator, Mapping, Union +from typing import Union from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import Checkpoint diff --git a/libs/langgraph/langgraph/pregel/messages.py b/libs/langgraph/langgraph/pregel/messages.py index 16d0904db..fa9ace1e1 100644 --- a/libs/langgraph/langgraph/pregel/messages.py +++ b/libs/langgraph/langgraph/pregel/messages.py @@ -1,12 +1,8 @@ +from collections.abc import AsyncIterator, Iterator, Sequence from typing import ( Any, - AsyncIterator, Callable, - Dict, - Iterator, - List, Optional, - Sequence, TypeVar, Union, cast, @@ -115,13 +111,13 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler): def on_chain_start( self, - serialized: Dict[str, Any], - inputs: Dict[str, Any], + serialized: dict[str, Any], + inputs: dict[str, Any], *, run_id: UUID, parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, + tags: Optional[list[str]] = None, + metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: if ( diff --git a/libs/langgraph/langgraph/pregel/protocol.py b/libs/langgraph/langgraph/pregel/protocol.py index 5a27f417a..85bd724ea 100644 --- a/libs/langgraph/langgraph/pregel/protocol.py +++ b/libs/langgraph/langgraph/pregel/protocol.py @@ -1,10 +1,8 @@ from abc import ABC, abstractmethod +from collections.abc import AsyncIterator, Iterator, Sequence from typing import ( Any, - AsyncIterator, - Iterator, Optional, - Sequence, Union, ) diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/read.py index e57620ec7..64ad9c966 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/read.py @@ -1,14 +1,10 @@ from __future__ import annotations +from collections.abc import AsyncIterator, Iterator, Mapping, Sequence from functools import cached_property from typing import ( Any, - AsyncIterator, Callable, - Iterator, - Mapping, - Optional, - Sequence, Union, ) @@ -37,11 +33,11 @@ class ChannelRead(RunnableCallable): """Implements the logic for reading state from CONFIG_KEY_READ. Usable both as a runnable as well as a static method to call imperatively.""" - channel: Union[str, list[str]] + channel: str | list[str] fresh: bool = False - mapper: Optional[Callable[[Any], Any]] = None + mapper: Callable[[Any], Any] | None = None @property def config_specs(self) -> list[ConfigurableFieldSpec]: @@ -57,11 +53,11 @@ class ChannelRead(RunnableCallable): def __init__( self, - channel: Union[str, list[str]], + channel: str | list[str], *, fresh: bool = False, - mapper: Optional[Callable[[Any], Any]] = None, - tags: Optional[list[str]] = None, + mapper: Callable[[Any], Any] | None = None, + tags: list[str] | None = None, ) -> None: super().__init__( func=self._read, @@ -75,9 +71,7 @@ class ChannelRead(RunnableCallable): self.mapper = mapper self.channel = channel - def get_name( - self, suffix: Optional[str] = None, *, name: Optional[str] = None - ) -> str: + def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str: if name: pass elif isinstance(self.channel, str): @@ -100,9 +94,9 @@ class ChannelRead(RunnableCallable): def do_read( config: RunnableConfig, *, - select: Union[str, list[str]], + select: str | list[str], fresh: bool = False, - mapper: Optional[Callable[[Any], Any]] = None, + mapper: Callable[[Any], Any] | None = None, ) -> Any: try: read: READ_TYPE = config[CONF][CONFIG_KEY_READ] @@ -125,7 +119,7 @@ class PregelNode(Runnable): itself, but instead acts as a container for the components necessary to make a PregelExecutableTask for a node.""" - channels: Union[list[str], Mapping[str, str]] + channels: list[str] | Mapping[str, str] """The channels that will be passed as input to `bound`. If a list, the node will be invoked with the first of that isn't empty. If a dict, the keys are the names of the channels, and the values are the keys @@ -135,7 +129,7 @@ class PregelNode(Runnable): """If any of these channels is written to, this node will be triggered in the next step.""" - mapper: Optional[Callable[[Any], Any]] + mapper: Callable[[Any], Any] | None """A function to transform the input before passing it to `bound`.""" writers: list[Runnable] @@ -146,13 +140,13 @@ class PregelNode(Runnable): """The main logic of the node. This will be invoked with the input from `channels`.""" - retry_policy: Optional[Sequence[RetryPolicy]] + retry_policy: Sequence[RetryPolicy] | None """The retry policies to use when invoking the node.""" - tags: Optional[Sequence[str]] + tags: Sequence[str] | None """Tags to attach to the node for tracing.""" - metadata: Optional[Mapping[str, Any]] + metadata: Mapping[str, Any] | None """Metadata to attach to the node for tracing.""" subgraphs: Sequence[PregelProtocol] @@ -161,15 +155,15 @@ class PregelNode(Runnable): def __init__( self, *, - channels: Union[list[str], Mapping[str, str]], + channels: list[str] | Mapping[str, str], triggers: Sequence[str], - mapper: Optional[Callable[[Any], Any]] = None, - writers: Optional[list[Runnable]] = None, - tags: Optional[list[str]] = None, - metadata: Optional[Mapping[str, Any]] = None, - bound: Optional[Runnable[Any, Any]] = None, - retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None, - subgraphs: Optional[Sequence[PregelProtocol]] = None, + mapper: Callable[[Any], Any] | None = None, + writers: list[Runnable] | None = None, + tags: list[str] | None = None, + metadata: Mapping[str, Any] | None = None, + bound: Runnable[Any, Any] | None = None, + retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None, + subgraphs: Sequence[PregelProtocol] | None = None, ) -> None: self.channels = channels self.triggers = list(triggers) @@ -219,7 +213,7 @@ class PregelNode(Runnable): return writers @cached_property - def node(self) -> Optional[Runnable[Any, Any]]: + def node(self) -> Runnable[Any, Any] | None: """Get a runnable that combines `bound` and `writers`.""" writers = self.flat_writers if self.bound is DEFAULT_BOUND and not writers: @@ -245,12 +239,12 @@ class PregelNode(Runnable): ) def join(self, channels: Sequence[str]) -> PregelNode: - assert isinstance(channels, list) or isinstance( - channels, tuple - ), "channels must be a list or tuple" - assert isinstance( - self.channels, dict - ), "all channels must be named when using .join()" + assert isinstance(channels, list) or isinstance(channels, tuple), ( + "channels must be a list or tuple" + ) + assert isinstance(self.channels, dict), ( + "all channels must be named when using .join()" + ) return self.copy( update=dict( channels={ @@ -262,11 +256,9 @@ class PregelNode(Runnable): def __or__( self, - other: Union[ - Runnable[Any, Other], - Callable[[Any], Other], - Mapping[str, Runnable[Any, Other] | Callable[[Any], Other]], - ], + other: Runnable[Any, Other] + | Callable[[Any], Other] + | Mapping[str, Runnable[Any, Other] | Callable[[Any], Other]], ) -> PregelNode: if isinstance(other, Runnable) and ChannelWrite.is_writer(other): return self.copy(update=dict(writers=[*self.writers, other])) @@ -278,7 +270,7 @@ class PregelNode(Runnable): def pipe( self, *others: Runnable[Any, Other] | Callable[[Any], Other], - name: Optional[str] = None, + name: str | None = None, ) -> RunnableSerializable[Any, Other]: for other in others: self = self | other @@ -286,19 +278,17 @@ class PregelNode(Runnable): def __ror__( self, - other: Union[ - Runnable[Other, Any], - Callable[[Any], Other], - Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any]]], - ], + other: Runnable[Other, Any] + | Callable[[Any], Other] + | Mapping[str, Runnable[Other, Any] | Callable[[Other], Any]], ) -> RunnableSerializable: raise NotImplementedError() def invoke( self, input: Input, - config: Optional[RunnableConfig] = None, - **kwargs: Optional[Any], + config: RunnableConfig | None = None, + **kwargs: Any | None, ) -> Any: return self.bound.invoke( input, @@ -309,8 +299,8 @@ class PregelNode(Runnable): async def ainvoke( self, input: Input, - config: Optional[RunnableConfig] = None, - **kwargs: Optional[Any], + config: RunnableConfig | None = None, + **kwargs: Any | None, ) -> Any: return await self.bound.ainvoke( input, @@ -321,8 +311,8 @@ class PregelNode(Runnable): def stream( self, input: Input, - config: Optional[RunnableConfig] = None, - **kwargs: Optional[Any], + config: RunnableConfig | None = None, + **kwargs: Any | None, ) -> Iterator[Any]: yield from self.bound.stream( input, @@ -333,8 +323,8 @@ class PregelNode(Runnable): async def astream( self, input: Input, - config: Optional[RunnableConfig] = None, - **kwargs: Optional[Any], + config: RunnableConfig | None = None, + **kwargs: Any | None, ) -> AsyncIterator[Any]: async for item in self.bound.astream( input, diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index 13cc7af4f..ba7cdb2c6 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -1,11 +1,9 @@ +from collections.abc import AsyncIterator, Iterator, Sequence from dataclasses import asdict from typing import ( Any, - AsyncIterator, - Iterator, Literal, Optional, - Sequence, Union, cast, ) diff --git a/libs/langgraph/langgraph/pregel/retry.py b/libs/langgraph/langgraph/pregel/retry.py index 1fc3b16ea..78c2f8b0c 100644 --- a/libs/langgraph/langgraph/pregel/retry.py +++ b/libs/langgraph/langgraph/pregel/retry.py @@ -3,8 +3,9 @@ import logging import random import sys import time +from collections.abc import Sequence from dataclasses import replace -from typing import Any, Optional, Sequence +from typing import Any, Optional from langgraph.constants import ( CONF, diff --git a/libs/langgraph/langgraph/pregel/runner.py b/libs/langgraph/langgraph/pregel/runner.py index fc12fc685..6a5992d04 100644 --- a/libs/langgraph/langgraph/pregel/runner.py +++ b/libs/langgraph/langgraph/pregel/runner.py @@ -3,18 +3,13 @@ import concurrent.futures import threading import time import weakref +from collections.abc import AsyncIterator, Awaitable, Iterable, Iterator, Sequence from functools import partial from typing import ( Any, - AsyncIterator, - Awaitable, Callable, Generic, - Iterable, - Iterator, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -72,7 +67,7 @@ class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]): callback: weakref.ref[ Callable[[PregelExecutableTask, Optional[BaseException]], None] ], - future_type: Type[F], + future_type: type[F], # used for generic typing, newer py supports FutureDict[...](...) ) -> None: super().__init__() @@ -475,7 +470,7 @@ def _exception( def _panic_or_proceed( futs: Union[set[concurrent.futures.Future], set[asyncio.Future]], *, - timeout_exc_cls: Type[Exception] = TimeoutError, + timeout_exc_cls: type[Exception] = TimeoutError, panic: bool = True, ) -> None: """Cancel remaining tasks if any failed, re-raise exception if panic is True.""" diff --git a/libs/langgraph/langgraph/pregel/validate.py b/libs/langgraph/langgraph/pregel/validate.py index 6b43f7b7c..7f638903f 100644 --- a/libs/langgraph/langgraph/pregel/validate.py +++ b/libs/langgraph/langgraph/pregel/validate.py @@ -1,4 +1,5 @@ -from typing import Any, Mapping, Optional, Sequence, Union +from collections.abc import Mapping, Sequence +from typing import Any, Optional, Union from langgraph.channels.base import BaseChannel from langgraph.constants import RESERVED diff --git a/libs/langgraph/langgraph/pregel/write.py b/libs/langgraph/langgraph/pregel/write.py index 234c1f5d7..9419a288d 100644 --- a/libs/langgraph/langgraph/pregel/write.py +++ b/libs/langgraph/langgraph/pregel/write.py @@ -1,11 +1,10 @@ from __future__ import annotations +from collections.abc import Sequence from typing import ( Any, Callable, NamedTuple, - Optional, - Sequence, TypeVar, Union, cast, @@ -32,12 +31,12 @@ class ChannelWriteEntry(NamedTuple): """Value to write, or PASSTHROUGH to use the input.""" skip_none: bool = False """Whether to skip writing if the value is None.""" - mapper: Optional[Callable] = None + mapper: Callable | None = None """Function to transform the value before writing.""" class ChannelWriteTupleEntry(NamedTuple): - mapper: Callable[[Any], Optional[Sequence[tuple[str, Any]]]] + mapper: Callable[[Any], Sequence[tuple[str, Any]] | None] """Function to extract tuples from value.""" value: Any = PASSTHROUGH """Value to write, or PASSTHROUGH to use the input.""" @@ -47,15 +46,15 @@ class ChannelWrite(RunnableCallable): """Implements the logic for sending writes to CONFIG_KEY_SEND. Can be used as a runnable or as a static method to call imperatively.""" - writes: list[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]] + writes: list[ChannelWriteEntry | ChannelWriteTupleEntry | Send] """Sequence of write entries or Send objects to write.""" def __init__( self, - writes: Sequence[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], + writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send], *, - tags: Optional[Sequence[str]] = None, # ignored - require_at_least_one_of: Optional[Sequence[str]] = None, # ignored + tags: Sequence[str] | None = None, # ignored + require_at_least_one_of: Sequence[str] | None = None, # ignored ): super().__init__( func=self._write, @@ -68,9 +67,7 @@ class ChannelWrite(RunnableCallable): list[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], writes ) - def get_name( - self, suffix: Optional[str] = None, *, name: Optional[str] = None - ) -> str: + def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str: if not name: name = f"ChannelWrite<{','.join(w.channel if isinstance(w, ChannelWriteEntry) else '...' if isinstance(w, ChannelWriteTupleEntry) else w.node for w in self.writes)}>" return super().get_name(suffix, name=name) @@ -120,8 +117,8 @@ class ChannelWrite(RunnableCallable): @staticmethod def do_write( config: RunnableConfig, - writes: Sequence[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], - require_at_least_one_of: Optional[Sequence[str]] = None, # ignored + writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send], + require_at_least_one_of: Sequence[str] | None = None, # ignored ) -> None: # validate for w in writes: diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 195acd4ff..d046c4dfb 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -1,18 +1,16 @@ import dataclasses import sys from collections import deque +from collections.abc import Hashable, Sequence from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, Generic, - Hashable, Literal, NamedTuple, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -118,7 +116,7 @@ class RetryPolicy(NamedTuple): jitter: bool = True """Whether to add random jitter to the interval between retries.""" retry_on: Union[ - Type[Exception], Sequence[Type[Exception]], Callable[[Exception], bool] + type[Exception], Sequence[type[Exception]], Callable[[Exception], bool] ] = default_retry_on """List of exception classes that should trigger a retry, or a callable that returns True for exceptions that should trigger a retry.""" diff --git a/libs/langgraph/langgraph/utils/config.py b/libs/langgraph/langgraph/utils/config.py index 20efec07b..fc39a4cff 100644 --- a/libs/langgraph/langgraph/utils/config.py +++ b/libs/langgraph/langgraph/utils/config.py @@ -1,6 +1,7 @@ from collections import ChainMap +from collections.abc import Sequence from os import getenv -from typing import Any, Optional, Sequence, cast +from typing import Any, Optional, cast from langchain_core.callbacks import ( AsyncCallbackManager, diff --git a/libs/langgraph/langgraph/utils/fields.py b/libs/langgraph/langgraph/utils/fields.py index e39f171bc..d94c58a64 100644 --- a/libs/langgraph/langgraph/utils/fields.py +++ b/libs/langgraph/langgraph/utils/fields.py @@ -1,9 +1,10 @@ import dataclasses -from typing import Any, Generator, Optional, Sequence, Type, Union, get_type_hints +from collections.abc import Generator, Sequence +from typing import Annotated, Any, Optional, Union, get_type_hints from pydantic import BaseModel from pydantic.v1 import BaseModel as BaseModelV1 -from typing_extensions import Annotated, NotRequired, ReadOnly, Required, get_origin +from typing_extensions import NotRequired, ReadOnly, Required, get_origin # NOTE: this is redefined here separately from langgraph.constants # to avoid a circular import @@ -68,7 +69,7 @@ def _is_readonly_type(type_: Any) -> bool: _DEFAULT_KEYS: frozenset[str] = frozenset() -def get_field_default(name: str, type_: Any, schema: Type[Any]) -> Any: +def get_field_default(name: str, type_: Any, schema: type[Any]) -> Any: """Determine the default value for a field in a state schema. This is based on: @@ -115,7 +116,7 @@ def get_field_default(name: str, type_: Any, schema: Type[Any]) -> Any: def get_enhanced_type_hints( - type: Type[Any], + type: type[Any], ) -> Generator[tuple[str, Any, Any, Optional[str]], None, None]: """Attempt to extract default values and descriptions from provided type, used for config schema.""" for name, typ in get_type_hints(type).items(): diff --git a/libs/langgraph/langgraph/utils/future.py b/libs/langgraph/langgraph/utils/future.py index a311133df..b4819245e 100644 --- a/libs/langgraph/langgraph/utils/future.py +++ b/libs/langgraph/langgraph/utils/future.py @@ -4,7 +4,8 @@ import contextvars import inspect import sys import types -from typing import Awaitable, Coroutine, Generator, Optional, TypeVar, Union, cast +from collections.abc import Awaitable, Coroutine, Generator +from typing import Optional, TypeVar, Union, cast T = TypeVar("T") AnyFuture = Union[asyncio.Future, concurrent.futures.Future] diff --git a/libs/langgraph/langgraph/utils/pydantic.py b/libs/langgraph/langgraph/utils/pydantic.py index 56cef30e6..66f434e5b 100644 --- a/libs/langgraph/langgraph/utils/pydantic.py +++ b/libs/langgraph/langgraph/utils/pydantic.py @@ -1,7 +1,7 @@ import sys import typing from dataclasses import is_dataclass -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import typing_extensions from pydantic import BaseModel @@ -11,7 +11,7 @@ from pydantic.v1 import BaseModel as BaseModelV1 def create_model( model_name: str, *, - field_definitions: Optional[Dict[str, Any]] = None, + field_definitions: Optional[dict[str, Any]] = None, root: Optional[Any] = None, ) -> Union[BaseModel, BaseModelV1]: """Create a pydantic model with the given field definitions. diff --git a/libs/langgraph/langgraph/utils/runnable.py b/libs/langgraph/langgraph/utils/runnable.py index c4ba527d6..a36acb41b 100644 --- a/libs/langgraph/langgraph/utils/runnable.py +++ b/libs/langgraph/langgraph/utils/runnable.py @@ -2,21 +2,22 @@ import asyncio import enum import inspect import sys +from collections.abc import ( + AsyncIterator, + Awaitable, + Coroutine, + Generator, + Iterator, + Sequence, +) from contextlib import AsyncExitStack, contextmanager from contextvars import Context, Token, copy_context from functools import partial, wraps from typing import ( Any, - AsyncIterator, - Awaitable, Callable, - Coroutine, - Generator, - Iterator, Optional, Protocol, - Sequence, - Tuple, Union, cast, ) @@ -278,7 +279,7 @@ class RunnableCallable(Runnable): if func_accepts_config is not None: self.func_accepts_config = func_accepts_config - self.func_accepts: dict[str, Tuple[str, Any]] = {} + self.func_accepts: dict[str, tuple[str, Any]] = {} else: params = inspect.signature(cast(Callable, func or afunc)).parameters diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index b581e4a60..1b3c6d297 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -41,11 +41,12 @@ types-requests = "^2.32.0.20240914" pycryptodome = "^3.21.0" [tool.ruff] -lint.select = [ "E", "F", "I", "TID251" ] +lint.select = [ "E", "F", "I", "TID251", "UP" ] lint.ignore = [ "E501" ] line-length = 88 indent-width = 4 extend-include = ["*.ipynb"] +target-version = "py39" [tool.ruff.format] quote-style = "double" diff --git a/libs/langgraph/tests/any_str.py b/libs/langgraph/tests/any_str.py index 7f63ea801..7d1b61554 100644 --- a/libs/langgraph/tests/any_str.py +++ b/libs/langgraph/tests/any_str.py @@ -1,5 +1,6 @@ import re -from typing import Any, Sequence, Union +from collections.abc import Sequence +from typing import Any, Union from typing_extensions import Self diff --git a/libs/langgraph/tests/conftest.py b/libs/langgraph/tests/conftest.py index 689ef6ab9..5bfec63bd 100644 --- a/libs/langgraph/tests/conftest.py +++ b/libs/langgraph/tests/conftest.py @@ -1,6 +1,7 @@ import sys +from collections.abc import AsyncIterator from contextlib import asynccontextmanager -from typing import AsyncIterator, Optional +from typing import Optional from uuid import UUID, uuid4 import pytest diff --git a/libs/langgraph/tests/fake_chat.py b/libs/langgraph/tests/fake_chat.py index d4a76ef7c..20e791d7e 100644 --- a/libs/langgraph/tests/fake_chat.py +++ b/libs/langgraph/tests/fake_chat.py @@ -1,5 +1,6 @@ import re -from typing import Any, AsyncIterator, Iterator, List, Optional, cast +from collections.abc import AsyncIterator, Iterator +from typing import Any, Optional, cast from langchain_core.callbacks import ( AsyncCallbackManagerForLLMRun, @@ -20,8 +21,8 @@ class FakeChatModel(GenericFakeChatModel): def _generate( self, - messages: List[BaseMessage], - stop: Optional[List[str]] = None, + messages: list[BaseMessage], + stop: Optional[list[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> ChatResult: @@ -42,8 +43,8 @@ class FakeChatModel(GenericFakeChatModel): def _stream( self, - messages: List[BaseMessage], - stop: Optional[List[str]] = None, + messages: list[BaseMessage], + stop: Optional[list[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: @@ -90,8 +91,8 @@ class FakeChatModel(GenericFakeChatModel): async def _astream( self, - messages: List[BaseMessage], - stop: Optional[List[str]] = None, + messages: list[BaseMessage], + stop: Optional[list[str]] = None, run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, **kwargs: Any, ) -> AsyncIterator[ChatGenerationChunk]: diff --git a/libs/langgraph/tests/test_channels.py b/libs/langgraph/tests/test_channels.py index b65036e54..0b18f7dd6 100644 --- a/libs/langgraph/tests/test_channels.py +++ b/libs/langgraph/tests/test_channels.py @@ -1,5 +1,6 @@ import operator -from typing import Sequence, Union +from collections.abc import Sequence +from typing import Union import pytest diff --git a/libs/langgraph/tests/test_checkpoint_migration.py b/libs/langgraph/tests/test_checkpoint_migration.py index 67e2efc1e..2c5dd1a32 100644 --- a/libs/langgraph/tests/test_checkpoint_migration.py +++ b/libs/langgraph/tests/test_checkpoint_migration.py @@ -1573,9 +1573,9 @@ def test_migrate_checkpoints(source: str, target: str) -> None: migrated["versions_seen"][c][v].split(".")[0] ) # check that the migrated checkpoint matches the target checkpoint - assert ( - migrated == target_checkpoint.checkpoint - ), "Checkpoint mismatch at index {}".format(idx) + assert migrated == target_checkpoint.checkpoint, ( + f"Checkpoint mismatch at index {idx}" + ) @NEEDS_CONTEXTVARS diff --git a/libs/langgraph/tests/test_io.py b/libs/langgraph/tests/test_io.py index cbc928a19..40fd18e60 100644 --- a/libs/langgraph/tests/test_io.py +++ b/libs/langgraph/tests/test_io.py @@ -1,4 +1,4 @@ -from typing import Iterator +from collections.abc import Iterator from langgraph.pregel.io import single diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index 7bba55ff5..1e16a0862 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -2,9 +2,10 @@ import json import operator import re import time +from collections.abc import Iterator from contextlib import contextmanager from dataclasses import replace -from typing import Annotated, Any, Iterator, Literal, Optional, Union, cast +from typing import Annotated, Any, Literal, Optional, Union, cast import httpx import pytest @@ -2829,9 +2830,9 @@ def test_state_graph_packets( # Define decision-making logic def should_continue(data: dict) -> str: assert isinstance(data["session"], httpx.Client) - assert ( - data["something_extra"] == "hi there" - ), "nodes can pass extra data to their cond edges, which isn't saved in state" + assert data["something_extra"] == "hi there", ( + "nodes can pass extra data to their cond edges, which isn't saved in state" + ) # Logic to decide whether to continue in the loop or exit if tool_calls := data["messages"][-1].tool_calls: return [Send("tools", tool_call) for tool_call in tool_calls] diff --git a/libs/langgraph/tests/test_large_cases_async.py b/libs/langgraph/tests/test_large_cases_async.py index 4f5c688aa..be3eb0d7b 100644 --- a/libs/langgraph/tests/test_large_cases_async.py +++ b/libs/langgraph/tests/test_large_cases_async.py @@ -2,11 +2,11 @@ import asyncio import operator import re import sys +from collections.abc import AsyncIterator from contextlib import asynccontextmanager from typing import ( Annotated, Any, - AsyncIterator, Literal, Optional, Union, @@ -3805,7 +3805,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None: docs: Annotated[list[str], operator.add] async def rewrite_query(data: State) -> State: - return {"query": f'query: {data["query"]}'} + return {"query": f"query: {data['query']}"} async def retriever_one(data: State) -> State: await asyncio.sleep(0.1) diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 5bac66252..9bbbad29f 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -14,6 +14,7 @@ import time import uuid import warnings from collections import Counter, deque +from collections.abc import Generator, Iterator, Sequence from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager from dataclasses import dataclass, field @@ -22,14 +23,8 @@ from random import randrange from typing import ( Annotated, Any, - Dict, - Generator, - Iterator, - List, Literal, Optional, - Sequence, - Tuple, Union, get_type_hints, ) @@ -249,7 +244,7 @@ def test_checkpoint_errors() -> None: class FaultyPutWritesCheckpointer(InMemorySaver): def put_writes( - self, config: RunnableConfig, writes: List[Tuple[str, Any]], task_id: str + self, config: RunnableConfig, writes: list[tuple[str, Any]], task_id: str ) -> RunnableConfig: raise ValueError("Faulty put_writes") @@ -454,7 +449,7 @@ def test_reducer_before_first_node() -> None: class State(TypedDict): hello: str - messages: Annotated[List[str], add_messages] + messages: Annotated[list[str], add_messages] def node_a(state: State) -> State: assert state == { @@ -1137,7 +1132,7 @@ def test_pending_writes_resume( value: Annotated[int, operator.add] class AwhileMaker: - def __init__(self, sleep: float, rtn: Union[Dict, Exception]) -> None: + def __init__(self, sleep: float, rtn: Union[dict, Exception]) -> None: self.sleep = sleep self.rtn = rtn self.reset() @@ -3229,7 +3224,7 @@ def test_nested_pydantic_models(version: str) -> None: validated_age: Annotated[int, Field(gt=0, lt=120)] # Generic containers with validators - decimal_list: List[decimal.Decimal] + decimal_list: list[decimal.Decimal] id_tuple: tuple[uuid.UUID, uuid.UUID] inputs = { @@ -4756,7 +4751,7 @@ def test_xray_lance(snapshot: SnapshotAssertion): return f"Name: {self.name}\nRole: {self.role}\nAffiliation: {self.affiliation}\nDescription: {self.description}\n" class Perspectives(BaseModel): - analysts: List[Analyst] = Field( + analysts: list[Analyst] = Field( description="Comprehensive list of investment analysts with their roles and affiliations.", ) @@ -4775,15 +4770,15 @@ def test_xray_lance(snapshot: SnapshotAssertion): ) class InterviewState(TypedDict): - messages: Annotated[List[AnyMessage], add_messages] + messages: Annotated[list[AnyMessage], add_messages] analyst: Analyst section: Section class ResearchGraphState(TypedDict): - analysts: List[Analyst] + analysts: list[Analyst] topic: str max_analysts: int - sections: List[Section] + sections: list[Section] interviews: Annotated[list, operator.add] # Conditional edge @@ -7756,7 +7751,7 @@ def test_parallel_interrupts( class ChildState(BaseModel): prompt: str = Field(..., description="What is going to be asked to the user?") human_input: Optional[str] = Field(None, description="What the human said") - human_inputs: Annotated[List[str], operator.add] = Field( + human_inputs: Annotated[list[str], operator.add] = Field( default_factory=list, description="All of my messages" ) @@ -7777,10 +7772,10 @@ def test_parallel_interrupts( # --- PARENT GRAPH --- class ParentState(BaseModel): - prompts: List[str] = Field( + prompts: list[str] = Field( ..., description="What is going to be asked to the user?" ) - human_inputs: Annotated[List[str], operator.add] = Field( + human_inputs: Annotated[list[str], operator.add] = Field( default_factory=list, description="All of my messages" ) @@ -7933,7 +7928,7 @@ def test_parallel_interrupts_double( class ChildState(BaseModel): prompt: str = Field(..., description="What is going to be asked to the user?") human_input: Optional[str] = Field(None, description="What the human said") - human_inputs: Annotated[List[str], operator.add] = Field( + human_inputs: Annotated[list[str], operator.add] = Field( default_factory=list, description="All of my messages" ) @@ -7961,10 +7956,10 @@ def test_parallel_interrupts_double( # --- PARENT GRAPH --- class ParentState(BaseModel): - prompts: List[str] = Field( + prompts: list[str] = Field( ..., description="What is going to be asked to the user?" ) - human_inputs: Annotated[List[str], operator.add] = Field( + human_inputs: Annotated[list[str], operator.add] = Field( default_factory=list, description="All of my messages" ) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 987732b90..bceac222b 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -8,20 +8,15 @@ import random import sys import uuid from collections import Counter, deque +from collections.abc import AsyncGenerator, AsyncIterator, Generator from contextlib import asynccontextmanager, contextmanager from dataclasses import replace from time import perf_counter from typing import ( Annotated, Any, - AsyncGenerator, - AsyncIterator, - Dict, - Generator, - List, Literal, Optional, - Tuple, Union, ) from uuid import UUID @@ -113,7 +108,7 @@ async def test_checkpoint_errors() -> None: class FaultyPutWritesCheckpointer(InMemorySaver): async def aput_writes( - self, config: RunnableConfig, writes: List[Tuple[str, Any]], task_id: str + self, config: RunnableConfig, writes: list[tuple[str, Any]], task_id: str ) -> RunnableConfig: raise ValueError("Faulty put_writes") @@ -1959,7 +1954,7 @@ async def test_pending_writes_resume( value: Annotated[int, operator.add] class AwhileMaker: - def __init__(self, sleep: float, rtn: Union[Dict, Exception]) -> None: + def __init__(self, sleep: float, rtn: Union[dict, Exception]) -> None: self.sleep = sleep self.rtn = rtn self.reset() @@ -6241,7 +6236,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) -> assert result == {"count": N + 1} returned_doc = (await the_store.aget(namespace, doc_id)).value assert returned_doc == {**doc, "from_thread": thread_1, "some_val": 0} - assert len((await the_store.asearch(namespace))) == 1 + assert len(await the_store.asearch(namespace)) == 1 # Check results after another turn of the same thread result = await graph.ainvoke( @@ -6250,7 +6245,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) -> assert result == {"count": (N + 1) * 2} returned_doc = (await the_store.aget(namespace, doc_id)).value assert returned_doc == {**doc, "from_thread": thread_1, "some_val": N + 1} - assert len((await the_store.asearch(namespace))) == 1 + assert len(await the_store.asearch(namespace)) == 1 # Test with a different thread result = await graph.ainvoke( @@ -6264,7 +6259,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) -> "some_val": 0, } # Overwrites the whole doc assert ( - len((await the_store.asearch(namespace))) == 1 + len(await the_store.asearch(namespace)) == 1 ) # still overwriting the same one diff --git a/libs/langgraph/tests/test_retry.py b/libs/langgraph/tests/test_retry.py index 6ef10d4c7..940d5db18 100644 --- a/libs/langgraph/tests/test_retry.py +++ b/libs/langgraph/tests/test_retry.py @@ -226,9 +226,10 @@ def test_graph_with_jitter_retry_policy(): ) # Test graph execution with mocked random and sleep - with patch("random.uniform", return_value=0.05) as mock_random, patch( - "time.sleep" - ) as mock_sleep: + with ( + patch("random.uniform", return_value=0.05) as mock_random, + patch("time.sleep") as mock_sleep, + ): result = graph.invoke({"foo": ""}) # Verify retry behavior @@ -334,8 +335,9 @@ def test_graph_with_max_attempts_exceeded(): ) # Test graph execution - with patch("time.sleep") as mock_sleep, pytest.raises( - ValueError, match="Always fails" + with ( + patch("time.sleep") as mock_sleep, + pytest.raises(ValueError, match="Always fails"), ): graph.invoke({"foo": ""}) diff --git a/libs/langgraph/tests/test_runnable.py b/libs/langgraph/tests/test_runnable.py index 0a81be368..1189c6688 100644 --- a/libs/langgraph/tests/test_runnable.py +++ b/libs/langgraph/tests/test_runnable.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import pytest @@ -85,7 +85,7 @@ def test_runnable_callable_injectable_arguments() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: + def func_optional_store(inputs: Any, store: BaseStore | None) -> str: """Test function that accepts an optional store parameter.""" assert store is None return "success" @@ -159,12 +159,12 @@ async def test_runnable_callable_injectable_arguments_async() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: + def func_optional_store(inputs: Any, store: BaseStore | None) -> str: """Test function that accepts an optional store parameter.""" assert store is None return "success" - async def afunc_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: + async def afunc_optional_store(inputs: Any, store: BaseStore | None) -> str: """Async version of func_optional_store.""" assert store is None return "success" diff --git a/libs/langgraph/tests/test_state.py b/libs/langgraph/tests/test_state.py index d85ccef94..1e8c2c623 100644 --- a/libs/langgraph/tests/test_state.py +++ b/libs/langgraph/tests/test_state.py @@ -2,13 +2,13 @@ import inspect import operator import warnings from dataclasses import dataclass, field +from typing import Annotated, Any, Optional from typing import Annotated as Annotated2 -from typing import Any, Optional import pytest from langchain_core.runnables import RunnableConfig, RunnableLambda from pydantic.v1 import BaseModel -from typing_extensions import Annotated, NotRequired, Required, TypedDict +from typing_extensions import NotRequired, Required, TypedDict from langgraph.graph.state import StateGraph, _get_node_name, _warn_invalid_state_schema from langgraph.managed.shared_value import SharedValue diff --git a/libs/langgraph/tests/test_tracing_interops.py b/libs/langgraph/tests/test_tracing_interops.py index d06896bd5..27c5098ca 100644 --- a/libs/langgraph/tests/test_tracing_interops.py +++ b/libs/langgraph/tests/test_tracing_interops.py @@ -1,7 +1,7 @@ import json import sys import time -from typing import Any, Callable, Tuple, TypeVar +from typing import Any, Callable, TypeVar from unittest.mock import MagicMock import langsmith as ls @@ -35,7 +35,7 @@ T = TypeVar("T") def wait_for( - condition: Callable[[], Tuple[T, bool]], + condition: Callable[[], tuple[T, bool]], max_sleep_time: int = 10, sleep_time: int = 3, ) -> T: diff --git a/libs/langgraph/tests/test_utils.py b/libs/langgraph/tests/test_utils.py index 3549bd574..ebde2aa82 100644 --- a/libs/langgraph/tests/test_utils.py +++ b/libs/langgraph/tests/test_utils.py @@ -2,11 +2,10 @@ import functools import sys import uuid from typing import ( + Annotated, Any, Callable, - Dict, ForwardRef, - List, Literal, Optional, TypeVar, @@ -16,7 +15,7 @@ from unittest.mock import patch import langsmith import pytest -from typing_extensions import Annotated, NotRequired, Required, TypedDict +from typing_extensions import NotRequired, Required, TypedDict from langgraph.graph import END, StateGraph from langgraph.graph.graph import CompiledGraph @@ -150,9 +149,9 @@ def test_is_optional_type(): assert not _is_optional_type(int) assert _is_optional_type(Optional[Literal[1, 2, 3]]) assert not _is_optional_type(Literal[1, 2, 3]) - assert _is_optional_type(Optional[List[int]]) - assert _is_optional_type(Optional[Dict[str, int]]) - assert not _is_optional_type(List[Optional[int]]) + assert _is_optional_type(Optional[list[int]]) + assert _is_optional_type(Optional[dict[str, int]]) + assert not _is_optional_type(list[Optional[int]]) assert _is_optional_type(Union[Optional[str], Optional[int]]) assert _is_optional_type( Union[ @@ -177,8 +176,8 @@ def test_is_optional_type(): assert _is_optional_type(Optional[ForwardRef("MyClass")]) assert not _is_optional_type(ForwardRef("MyClass")) - assert _is_optional_type(Optional[Union[List[int], Dict[str, Optional[int]]]]) - assert not _is_optional_type(Union[List[int], Dict[str, Optional[int]]]) + assert _is_optional_type(Optional[Union[list[int], dict[str, Optional[int]]]]) + assert not _is_optional_type(Union[list[int], dict[str, Optional[int]]]) assert _is_optional_type(Optional[Callable[[int], str]]) assert not _is_optional_type(Callable[[int], Optional[str]]) From b6ea73ff2497e4e548ed4c7b7d171aaf9596d50a Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 21 Apr 2025 21:01:27 -0700 Subject: [PATCH 2/5] linting for 3.12 --- libs/langgraph/langgraph/pregel/read.py | 12 ++++++------ libs/langgraph/tests/test_checkpoint_migration.py | 6 +++--- libs/langgraph/tests/test_large_cases.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/read.py index 64ad9c966..6fb2f72cb 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/read.py @@ -239,12 +239,12 @@ class PregelNode(Runnable): ) def join(self, channels: Sequence[str]) -> PregelNode: - assert isinstance(channels, list) or isinstance(channels, tuple), ( - "channels must be a list or tuple" - ) - assert isinstance(self.channels, dict), ( - "all channels must be named when using .join()" - ) + assert isinstance(channels, list) or isinstance( + channels, tuple + ), "channels must be a list or tuple" + assert isinstance( + self.channels, dict + ), "all channels must be named when using .join()" return self.copy( update=dict( channels={ diff --git a/libs/langgraph/tests/test_checkpoint_migration.py b/libs/langgraph/tests/test_checkpoint_migration.py index 2c5dd1a32..21727ba0a 100644 --- a/libs/langgraph/tests/test_checkpoint_migration.py +++ b/libs/langgraph/tests/test_checkpoint_migration.py @@ -1573,9 +1573,9 @@ def test_migrate_checkpoints(source: str, target: str) -> None: migrated["versions_seen"][c][v].split(".")[0] ) # check that the migrated checkpoint matches the target checkpoint - assert migrated == target_checkpoint.checkpoint, ( - f"Checkpoint mismatch at index {idx}" - ) + assert ( + migrated == target_checkpoint.checkpoint + ), f"Checkpoint mismatch at index {idx}" @NEEDS_CONTEXTVARS diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index 1e16a0862..195fe6c53 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -2830,9 +2830,9 @@ def test_state_graph_packets( # Define decision-making logic def should_continue(data: dict) -> str: assert isinstance(data["session"], httpx.Client) - assert data["something_extra"] == "hi there", ( - "nodes can pass extra data to their cond edges, which isn't saved in state" - ) + assert ( + data["something_extra"] == "hi there" + ), "nodes can pass extra data to their cond edges, which isn't saved in state" # Logic to decide whether to continue in the loop or exit if tool_calls := data["messages"][-1].tool_calls: return [Send("tools", tool_call) for tool_call in tool_calls] From cba7d217321cc5cbb7e055aacc8875fe3a262d27 Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Mon, 21 Apr 2025 21:14:14 -0700 Subject: [PATCH 3/5] fix tests? --- libs/langgraph/pyproject.toml | 2 +- libs/langgraph/tests/test_channels.py | 4 ++-- libs/langgraph/tests/test_runnable.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 1b3c6d297..f3e2a9b1c 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -42,7 +42,7 @@ pycryptodome = "^3.21.0" [tool.ruff] lint.select = [ "E", "F", "I", "TID251", "UP" ] -lint.ignore = [ "E501" ] +lint.ignore = [ "E501", "UP007" ] line-length = 88 indent-width = 4 extend-include = ["*.ipynb"] diff --git a/libs/langgraph/tests/test_channels.py b/libs/langgraph/tests/test_channels.py index 0b18f7dd6..c8d679ab8 100644 --- a/libs/langgraph/tests/test_channels.py +++ b/libs/langgraph/tests/test_channels.py @@ -34,7 +34,7 @@ def test_last_value() -> None: def test_topic() -> None: channel = Topic(str).from_checkpoint(MISSING) - assert channel.ValueType is Sequence[str] + assert channel.ValueType == Sequence[str] assert channel.UpdateType is Union[str, list[str]] assert channel.update(["a", "b"]) @@ -58,7 +58,7 @@ def test_topic() -> None: def test_topic_accumulate() -> None: channel = Topic(str, accumulate=True).from_checkpoint(MISSING) - assert channel.ValueType is Sequence[str] + assert channel.ValueType == Sequence[str] assert channel.UpdateType is Union[str, list[str]] assert channel.update(["a", "b"]) diff --git a/libs/langgraph/tests/test_runnable.py b/libs/langgraph/tests/test_runnable.py index 1189c6688..0a81be368 100644 --- a/libs/langgraph/tests/test_runnable.py +++ b/libs/langgraph/tests/test_runnable.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any +from typing import Any, Optional import pytest @@ -85,7 +85,7 @@ def test_runnable_callable_injectable_arguments() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: BaseStore | None) -> str: + def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: """Test function that accepts an optional store parameter.""" assert store is None return "success" @@ -159,12 +159,12 @@ async def test_runnable_callable_injectable_arguments_async() -> None: """ # Test Optional[BaseStore] annotation. - def func_optional_store(inputs: Any, store: BaseStore | None) -> str: + def func_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: """Test function that accepts an optional store parameter.""" assert store is None return "success" - async def afunc_optional_store(inputs: Any, store: BaseStore | None) -> str: + async def afunc_optional_store(inputs: Any, store: Optional[BaseStore]) -> str: """Async version of func_optional_store.""" assert store is None return "success" From 8977a35060f2a86bf10899075eab02ef787f4566 Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Tue, 22 Apr 2025 13:01:40 -0700 Subject: [PATCH 4/5] linting post merge --- libs/langgraph/langgraph/pregel/__init__.py | 7 ++----- libs/langgraph/langgraph/pregel/draw.py | 3 ++- libs/langgraph/langgraph/pregel/write.py | 1 + libs/langgraph/tests/test_pregel.py | 1 + libs/langgraph/tests/test_pydantic.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 2a369aa17..f9738d211 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -557,7 +557,7 @@ class Pregel(PregelProtocol): self.validate() def get_graph( - self, config: RunnableConfig | None = None, *, xray: int | bool = Fals + self, config: RunnableConfig | None = None, *, xray: int | bool = False ) -> Graph: """Returns a drawable representation of the computation graph.""" # gather subgraphs @@ -585,10 +585,7 @@ class Pregel(PregelProtocol): ) async def aget_graph( - self, - config: RunnableConfig | None = None, - *, - xray: int | bool = False + self, config: RunnableConfig | None = None, *, xray: int | bool = False ) -> Graph: """Returns a drawable representation of the computation graph.""" diff --git a/libs/langgraph/langgraph/pregel/draw.py b/libs/langgraph/langgraph/pregel/draw.py index 596935f79..a32aaeded 100644 --- a/libs/langgraph/langgraph/pregel/draw.py +++ b/libs/langgraph/langgraph/pregel/draw.py @@ -1,5 +1,6 @@ from collections import defaultdict -from typing import Any, Mapping, Optional, Sequence, Union, cast +from collections.abc import Mapping, Sequence +from typing import Any, Optional, Union, cast from langchain_core.runnables.config import RunnableConfig from langchain_core.runnables.graph import Graph, Node diff --git a/libs/langgraph/langgraph/pregel/write.py b/libs/langgraph/langgraph/pregel/write.py index ed8955474..98b6dd587 100644 --- a/libs/langgraph/langgraph/pregel/write.py +++ b/libs/langgraph/langgraph/pregel/write.py @@ -5,6 +5,7 @@ from typing import ( Any, Callable, NamedTuple, + Optional, TypeVar, Union, cast, diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index b6333d0de..ce6474ca1 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -3090,6 +3090,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic_inp } } + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_in_one_fan_out_state_graph_waiting_edge_plus_regular( request: pytest.FixtureRequest, checkpointer_name: str diff --git a/libs/langgraph/tests/test_pydantic.py b/libs/langgraph/tests/test_pydantic.py index ad94724b5..53e6fe4e7 100644 --- a/libs/langgraph/tests/test_pydantic.py +++ b/libs/langgraph/tests/test_pydantic.py @@ -6,7 +6,7 @@ import re import sys import uuid from enum import Enum -from typing import Annotated, List, Literal, Optional, Union +from typing import Annotated, Literal, Optional, Union import pytest @@ -183,7 +183,7 @@ def test_nested_pydantic_models(version: str) -> None: validated_age: Annotated[int, Field(gt=0, lt=120)] # Generic containers with validators - decimal_list: List[decimal.Decimal] + decimal_list: list[decimal.Decimal] id_tuple: tuple[uuid.UUID, uuid.UUID] inputs = { From edd7d608cd918b70c1089b689513c30286fb9a26 Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Tue, 22 Apr 2025 13:40:53 -0700 Subject: [PATCH 5/5] final linting --- .../langgraph/channels/dynamic_barrier_value.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/langgraph/langgraph/channels/dynamic_barrier_value.py b/libs/langgraph/langgraph/channels/dynamic_barrier_value.py index ea6ca9815..c2c3c026f 100644 --- a/libs/langgraph/langgraph/channels/dynamic_barrier_value.py +++ b/libs/langgraph/langgraph/channels/dynamic_barrier_value.py @@ -1,4 +1,4 @@ -from collections.abc import Sequence +from collections.abc import Sequence, Set from typing import Any, Generic, NamedTuple, Optional, Union from typing_extensions import Self @@ -9,11 +9,11 @@ from langgraph.errors import EmptyChannelError, InvalidUpdateError class WaitForNames(NamedTuple): - names: set[Any] + names: Set[Any] class DynamicBarrierValue( - Generic[Value], BaseChannel[Value, Union[Value, WaitForNames], set[Value]] + Generic[Value], BaseChannel[Value, Union[Value, WaitForNames], Set[Value]] ): """A channel that switches between two states @@ -26,7 +26,7 @@ class DynamicBarrierValue( __slots__ = ("names", "seen") - names: Optional[set[Value]] + names: Optional[Set[Value]] seen: set[Value] def __init__(self, typ: type[Value]) -> None: @@ -55,11 +55,11 @@ class DynamicBarrierValue( empty.seen = self.seen.copy() return empty - def checkpoint(self) -> tuple[Optional[set[Value]], set[Value]]: + def checkpoint(self) -> tuple[Optional[Set[Value]], set[Value]]: return (self.names, self.seen) def from_checkpoint( - self, checkpoint: tuple[Optional[set[Value]], set[Value]] + self, checkpoint: tuple[Optional[Set[Value]], set[Value]] ) -> Self: empty = self.__class__(self.typ) empty.key = self.key