mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-12 04:37:51 +02:00
what would it look like to remove pydantic v1 support?
This commit is contained in:
@@ -14,7 +14,6 @@ from typing import (
|
||||
)
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
__all__ = ["SchemaCoercionMapper"]
|
||||
|
||||
@@ -45,7 +44,7 @@ class SchemaCoercionMapper:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schema: type[Any],
|
||||
schema: type[BaseModel],
|
||||
type_hints: Optional[dict[str, Any]] = None,
|
||||
*,
|
||||
max_depth: int = 12,
|
||||
@@ -63,30 +62,12 @@ class SchemaCoercionMapper:
|
||||
else get_type_hints(schema, localns={schema.__name__: schema})
|
||||
)
|
||||
|
||||
if issubclass(schema, BaseModelV1):
|
||||
self._fields = {
|
||||
n: self.type_hints.get(n, f.annotation)
|
||||
for n, f in schema.__fields__.items()
|
||||
}
|
||||
self._construct = schema.construct
|
||||
unhandled_attrs = (
|
||||
"__pre_root_validators__",
|
||||
"__post_root_validators__",
|
||||
"__validators__",
|
||||
)
|
||||
if any(getattr(schema, c, None) for c in unhandled_attrs):
|
||||
self.coerce: Callable[[Any, Any], Union[BaseModelV1, BaseModel]] = (
|
||||
lambda v, _: schema(**v)
|
||||
)
|
||||
else:
|
||||
self.coerce = self._coerce
|
||||
|
||||
elif issubclass(schema, BaseModel):
|
||||
if issubclass(schema, BaseModel):
|
||||
self._fields = {
|
||||
n: self.type_hints.get(n, f.annotation)
|
||||
for n, f in schema.model_fields.items()
|
||||
}
|
||||
self._construct: Callable[..., Any] = schema.model_construct # type: ignore
|
||||
self._construct: Callable[..., Any] = schema.model_construct
|
||||
unhandled_attrs = ("validators", "field_validators", "root_validators")
|
||||
if (decorators := getattr(schema, "__pydantic_decorators__", None)) and any(
|
||||
getattr(decorators, attr, None) for attr in unhandled_attrs
|
||||
@@ -94,9 +75,8 @@ class SchemaCoercionMapper:
|
||||
self.coerce = lambda v, _: schema.model_validate(v)
|
||||
else:
|
||||
self.coerce = self._coerce
|
||||
|
||||
else:
|
||||
raise TypeError("Schema is neither a Pydantic v1 nor v2 model.")
|
||||
raise TypeError("Schema must be a Pydantic V2 model.")
|
||||
|
||||
self._field_coercers: Optional[dict[str, Callable[[Any, int], Any]]] = None
|
||||
|
||||
@@ -138,14 +118,12 @@ class SchemaCoercionMapper:
|
||||
|
||||
if isclass(field_type):
|
||||
# This is needed bcs. of issubclass issues on older versions of python
|
||||
is_class_ = True
|
||||
try:
|
||||
is_bm_v2 = issubclass(field_type, BaseModel)
|
||||
is_bm_subclass = issubclass(field_type, BaseModel)
|
||||
except TypeError:
|
||||
# python < 3.11 issue.
|
||||
is_class_ = False
|
||||
is_bm_v2 = False
|
||||
if is_bm_v2 or (is_class_ and issubclass(field_type, BaseModelV1)):
|
||||
is_bm_subclass = False
|
||||
if is_bm_subclass:
|
||||
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
|
||||
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ from typing import (
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from pydantic import BaseModel
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._api.deprecation import LangGraphDeprecationWarning
|
||||
@@ -642,7 +641,7 @@ class StateGraph(Graph):
|
||||
self.input
|
||||
if len(self.channels) > 1
|
||||
and isclass(self.input)
|
||||
and issubclass(self.input, (BaseModel, BaseModelV1))
|
||||
and issubclass(self.input, BaseModel)
|
||||
else None
|
||||
),
|
||||
nodes={},
|
||||
@@ -1026,7 +1025,7 @@ def _pick_mapper(
|
||||
if isclass(schema):
|
||||
if issubclass(schema, dict):
|
||||
return None
|
||||
if issubclass(schema, (BaseModel, BaseModelV1)):
|
||||
if issubclass(schema, BaseModel):
|
||||
return SchemaCoercionMapper(schema, type_hints=type_hints)
|
||||
return partial(_coerce_state, schema)
|
||||
|
||||
@@ -1204,7 +1203,7 @@ def _get_schema(
|
||||
channels: dict,
|
||||
name: str,
|
||||
) -> type[BaseModel]:
|
||||
if isclass(typ) and issubclass(typ, (BaseModel, BaseModelV1)):
|
||||
if isclass(typ) and issubclass(typ, BaseModel):
|
||||
return typ
|
||||
else:
|
||||
keys = list(schemas[typ].keys())
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 NotRequired, ReadOnly, Required, get_origin
|
||||
|
||||
# NOTE: this is redefined here separately from langgraph.constants
|
||||
@@ -158,12 +157,7 @@ def get_enhanced_type_hints(
|
||||
|
||||
def get_update_as_tuples(input: Any, keys: Sequence[str]) -> list[tuple[str, Any]]:
|
||||
"""Get Pydantic state update as a list of (key, value) tuples."""
|
||||
# Pydantic v1
|
||||
if isinstance(input, BaseModelV1):
|
||||
keep: Optional[set[str]] = input.__fields_set__
|
||||
defaults = {k: v.default for k, v in input.__fields__.items()}
|
||||
# Pydantic v2
|
||||
elif isinstance(input, BaseModel):
|
||||
if isinstance(input, BaseModel):
|
||||
keep = input.model_fields_set
|
||||
defaults = {k: v.default for k, v in input.model_fields.items()}
|
||||
else:
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import sys
|
||||
import typing
|
||||
from dataclasses import is_dataclass
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Optional
|
||||
|
||||
import typing_extensions
|
||||
from pydantic import BaseModel
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
|
||||
def create_model(
|
||||
@@ -13,7 +12,7 @@ def create_model(
|
||||
*,
|
||||
field_definitions: Optional[dict[str, Any]] = None,
|
||||
root: Optional[Any] = None,
|
||||
) -> Union[BaseModel, BaseModelV1]:
|
||||
) -> type[BaseModel]:
|
||||
"""Create a pydantic model with the given field definitions.
|
||||
|
||||
Args:
|
||||
@@ -21,24 +20,14 @@ def create_model(
|
||||
field_definitions: The field definitions for the model.
|
||||
root: Type for a root model (RootModel)
|
||||
"""
|
||||
try:
|
||||
# for langchain-core >= 0.3.0
|
||||
from langchain_core.utils.pydantic import create_model_v2
|
||||
# for langchain-core >= 0.3.0
|
||||
from langchain_core.utils.pydantic import create_model_v2
|
||||
|
||||
return create_model_v2(
|
||||
model_name,
|
||||
field_definitions=field_definitions,
|
||||
root=root,
|
||||
)
|
||||
except ImportError:
|
||||
# for langchain-core < 0.3.0
|
||||
from langchain_core.runnables.utils import create_model
|
||||
|
||||
v1_kwargs = {}
|
||||
if root is not None:
|
||||
v1_kwargs["__root__"] = root
|
||||
|
||||
return create_model(model_name, **v1_kwargs, **(field_definitions or {}))
|
||||
return create_model_v2(
|
||||
model_name,
|
||||
field_definitions=field_definitions,
|
||||
root=root,
|
||||
)
|
||||
|
||||
|
||||
def is_supported_by_pydantic(type_: Any) -> bool:
|
||||
@@ -51,8 +40,6 @@ def is_supported_by_pydantic(type_: Any) -> bool:
|
||||
if is_dataclass(type_):
|
||||
return True
|
||||
|
||||
# Pydantic does not support mixing .v1 and root namespaces, so
|
||||
# we only check for BaseModel (not pydantic.v1.BaseModel).
|
||||
if isinstance(type_, type) and issubclass(type_, BaseModel):
|
||||
return True
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1367,7 +1367,7 @@ typing-extensions = ">=4.7"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.24"
|
||||
version = "2.0.25"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
|
||||
@@ -6,22 +6,18 @@ from pydantic import BaseModel
|
||||
# define these objects to avoid importing langchain_core.agents
|
||||
# and therefore avoid relying on core Pydantic version
|
||||
class AgentAction(BaseModel):
|
||||
"""
|
||||
Represents a request to execute an action by an agent.
|
||||
|
||||
The action consists of the name of the tool to execute and the input to pass
|
||||
to the tool. The log is used to pass along extra information about the action.
|
||||
"""
|
||||
|
||||
tool: str
|
||||
tool_input: Union[str, dict]
|
||||
log: str
|
||||
type: Literal["AgentAction"] = "AgentAction"
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"description": (
|
||||
"""Represents a request to execute an action by an agent.
|
||||
|
||||
The action consists of the name of the tool to execute and the input to pass
|
||||
to the tool. The log is used to pass along extra information about the action."""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AgentFinish(BaseModel):
|
||||
"""Final return value of an ActionAgent.
|
||||
@@ -32,12 +28,3 @@ class AgentFinish(BaseModel):
|
||||
return_values: dict
|
||||
log: str
|
||||
type: Literal["AgentFinish"] = "AgentFinish"
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"description": (
|
||||
"""Final return value of an ActionAgent.
|
||||
|
||||
Agents return an AgentFinish when they have reached a stopping condition."""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,11 @@ from langchain_core.messages import (
|
||||
ToolMessage,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.graph import add_messages
|
||||
from langgraph.graph.message import REMOVE_ALL_MESSAGES, MessagesState
|
||||
from langgraph.graph.state import END, START, StateGraph
|
||||
from tests.conftest import IS_LANGCHAIN_CORE_030_OR_GREATER
|
||||
from tests.messages import _AnyIdHumanMessage
|
||||
|
||||
_, CORE_MINOR, CORE_PATCH = (int(v) for v in langchain_core.__version__.split("."))
|
||||
@@ -175,19 +173,11 @@ def test_delete_all():
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
MESSAGES_STATE_SCHEMAS = [MessagesState]
|
||||
if IS_LANGCHAIN_CORE_030_OR_GREATER:
|
||||
class MessagesStatePydantic(BaseModel):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
class MessagesStatePydantic(BaseModel):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
MESSAGES_STATE_SCHEMAS.append(MessagesStatePydantic)
|
||||
else:
|
||||
|
||||
class MessagesStatePydanticV1(BaseModelV1):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
MESSAGES_STATE_SCHEMAS.append(MessagesStatePydanticV1)
|
||||
MESSAGES_STATE_SCHEMAS = [MessagesState, MessagesStatePydantic]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("state_schema", MESSAGES_STATE_SCHEMAS)
|
||||
|
||||
@@ -33,6 +33,7 @@ from langchain_core.runnables import (
|
||||
)
|
||||
from langchain_core.runnables.graph import Edge
|
||||
from langsmith import traceable
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
||||
from pytest_mock import MockerFixture
|
||||
from syrupy import SnapshotAssertion
|
||||
from typing_extensions import TypedDict
|
||||
@@ -2602,8 +2603,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1(
|
||||
request: pytest.FixtureRequest,
|
||||
checkpointer_name: str,
|
||||
) -> None:
|
||||
from pydantic.v1 import BaseModel, ValidationError
|
||||
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
setup = mocker.Mock()
|
||||
teardown = mocker.Mock()
|
||||
@@ -2642,8 +2641,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1(
|
||||
yo: int
|
||||
|
||||
class State(BaseModel):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
query: str
|
||||
inner: Annotated[InnerObject, lambda x, y: y]
|
||||
@@ -2777,11 +2775,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
|
||||
request: pytest.FixtureRequest,
|
||||
checkpointer_name: str,
|
||||
) -> None:
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
IS_V1 = BaseModel is BaseModelV1
|
||||
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
setup = mocker.Mock()
|
||||
teardown = mocker.Mock()
|
||||
@@ -2819,28 +2812,14 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
|
||||
class InnerObject(BaseModel):
|
||||
yo: int
|
||||
|
||||
if IS_V1:
|
||||
class State(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
class State(BaseModel):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
query: str
|
||||
inner: Annotated[InnerObject, lambda x, y: y]
|
||||
answer: Optional[str] = None
|
||||
docs: Annotated[list[str], sorted_add]
|
||||
client: Annotated[httpx.Client, Context(make_httpx_client)]
|
||||
|
||||
else:
|
||||
|
||||
class State(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
query: str
|
||||
inner: Annotated[InnerObject, lambda x, y: y]
|
||||
answer: Optional[str] = None
|
||||
docs: Annotated[list[str], sorted_add]
|
||||
client: Annotated[httpx.Client, Context(make_httpx_client)]
|
||||
query: str
|
||||
inner: Annotated[InnerObject, lambda x, y: y]
|
||||
answer: Optional[str] = None
|
||||
docs: Annotated[list[str], sorted_add]
|
||||
client: Annotated[httpx.Client, Context(make_httpx_client)]
|
||||
|
||||
class StateUpdate(BaseModel):
|
||||
query: Optional[str] = None
|
||||
@@ -2966,8 +2945,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic_inp
|
||||
request: pytest.FixtureRequest,
|
||||
checkpointer_name: str,
|
||||
) -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
|
||||
def sorted_add(
|
||||
@@ -4415,7 +4392,6 @@ def test_remove_message_from_node():
|
||||
|
||||
def test_xray_lance(snapshot: SnapshotAssertion):
|
||||
from langchain_core.messages import AnyMessage, HumanMessage
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Analyst(BaseModel):
|
||||
affiliation: str = Field(
|
||||
@@ -5533,8 +5509,6 @@ def test_dict_mixed_return() -> None:
|
||||
|
||||
|
||||
def test_command_pydantic_dataclass() -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
class PydanticState(BaseModel):
|
||||
foo: str
|
||||
|
||||
@@ -6351,9 +6325,7 @@ def test_double_interrupt_subgraph(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
|
||||
def test_multi_resume(
|
||||
request: pytest.FixtureRequest, checkpointer_name: str
|
||||
) -> None:
|
||||
def test_multi_resume(request: pytest.FixtureRequest, checkpointer_name: str) -> None:
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
|
||||
class ChildState(TypedDict):
|
||||
@@ -6362,11 +6334,11 @@ def test_multi_resume(
|
||||
human_inputs: list[str]
|
||||
|
||||
def get_human_input(state: ChildState):
|
||||
human_input = interrupt(state['prompt'])
|
||||
human_input = interrupt(state["prompt"])
|
||||
|
||||
return {
|
||||
'human_input': human_input,
|
||||
'human_inputs': [human_input],
|
||||
"human_input": human_input,
|
||||
"human_inputs": [human_input],
|
||||
}
|
||||
|
||||
child_graph = (
|
||||
@@ -6385,13 +6357,13 @@ def test_multi_resume(
|
||||
return [
|
||||
Send(
|
||||
"child_graph",
|
||||
{'prompt': prompt},
|
||||
{"prompt": prompt},
|
||||
)
|
||||
for prompt in state['prompts']
|
||||
for prompt in state["prompts"]
|
||||
]
|
||||
|
||||
def cleanup(state: ParentState):
|
||||
assert len(state['human_inputs']) == len(state["prompts"])
|
||||
assert len(state["human_inputs"]) == len(state["prompts"])
|
||||
|
||||
parent_graph = (
|
||||
StateGraph(ParentState)
|
||||
@@ -6404,21 +6376,19 @@ def test_multi_resume(
|
||||
)
|
||||
|
||||
thread_config: RunnableConfig = {
|
||||
'configurable': {
|
||||
'thread_id': uuid.uuid4(),
|
||||
"configurable": {
|
||||
"thread_id": uuid.uuid4(),
|
||||
},
|
||||
}
|
||||
|
||||
prompts = ['a', 'b', 'c', 'd', 'e']
|
||||
prompts = ["a", "b", "c", "d", "e"]
|
||||
|
||||
events = parent_graph.invoke(
|
||||
{'prompts': prompts},
|
||||
thread_config,
|
||||
stream_mode='values'
|
||||
{"prompts": prompts}, thread_config, stream_mode="values"
|
||||
)
|
||||
|
||||
assert len(events['__interrupt__']) == len(prompts)
|
||||
interrupt_values = {i.value for i in events['__interrupt__']}
|
||||
assert len(events["__interrupt__"]) == len(prompts)
|
||||
interrupt_values = {i.value for i in events["__interrupt__"]}
|
||||
assert interrupt_values == set(prompts)
|
||||
|
||||
resume_map: dict[str, str] = {
|
||||
@@ -6428,11 +6398,8 @@ def test_multi_resume(
|
||||
|
||||
result = parent_graph.invoke(Command(resume=resume_map), thread_config)
|
||||
assert result == {
|
||||
'prompts': prompts,
|
||||
'human_inputs': [
|
||||
f"human input for prompt {prompt}"
|
||||
for prompt in prompts
|
||||
],
|
||||
"prompts": prompts,
|
||||
"human_inputs": [f"human input for prompt {prompt}" for prompt in prompts],
|
||||
}
|
||||
|
||||
|
||||
@@ -7169,8 +7136,6 @@ def test_node_destinations() -> None:
|
||||
|
||||
|
||||
def test_pydantic_none_state_update() -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
class State(BaseModel):
|
||||
foo: Optional[str]
|
||||
|
||||
@@ -7182,8 +7147,6 @@ def test_pydantic_none_state_update() -> None:
|
||||
|
||||
|
||||
def test_pydantic_state_update_command() -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
class State(BaseModel):
|
||||
foo: Optional[str]
|
||||
|
||||
@@ -7215,8 +7178,6 @@ def test_pydantic_state_update_command() -> None:
|
||||
|
||||
|
||||
def test_pydantic_state_mutation() -> None:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Inner(BaseModel):
|
||||
a: int = 0
|
||||
|
||||
@@ -7249,8 +7210,6 @@ def test_pydantic_state_mutation() -> None:
|
||||
|
||||
|
||||
def test_pydantic_state_mutation_command() -> None:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Inner(BaseModel):
|
||||
a: int = 0
|
||||
|
||||
@@ -7529,8 +7488,6 @@ def test_interrupt_subgraph_reenter_checkpointer_true(
|
||||
|
||||
|
||||
def test_empty_invoke() -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
def reducer_merge_dicts(
|
||||
dict1: dict[Any, Any], dict2: dict[Any, Any]
|
||||
) -> dict[Any, Any]:
|
||||
@@ -7582,8 +7539,6 @@ def test_empty_invoke() -> None:
|
||||
def test_parallel_interrupts(
|
||||
request: pytest.FixtureRequest, checkpointer_name: str
|
||||
) -> None:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
|
||||
# --- CHILD GRAPH ---
|
||||
@@ -7759,8 +7714,6 @@ def test_parallel_interrupts(
|
||||
def test_parallel_interrupts_double(
|
||||
request: pytest.FixtureRequest, checkpointer_name: str
|
||||
) -> None:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
|
||||
# --- CHILD GRAPH ---
|
||||
@@ -8214,8 +8167,6 @@ def test_batch_update_as_input(
|
||||
|
||||
|
||||
def test_migration_graph(snapshot: SnapshotAssertion) -> None:
|
||||
from pydantic import BaseModel
|
||||
|
||||
class DummyState(BaseModel):
|
||||
pass_count: int = 0
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import pytest
|
||||
from langchain_core.language_models import GenericFakeChatModel
|
||||
from langchain_core.runnables import RunnableConfig, RunnableLambda, RunnablePassthrough
|
||||
from langchain_core.utils.aiter import aclosing
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
||||
from pytest_mock import MockerFixture
|
||||
from syrupy import SnapshotAssertion
|
||||
from typing_extensions import TypedDict
|
||||
@@ -4663,16 +4664,9 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch(
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["v1", "v2"])
|
||||
async def test_nested_pydantic_models(version: str) -> None:
|
||||
async def test_nested_pydantic_models() -> None:
|
||||
"""Test that nested Pydantic models are properly constructed from leaf nodes up."""
|
||||
|
||||
# Define nested Pydantic models
|
||||
if version == "v1":
|
||||
from pydantic.v1 import BaseModel, Field
|
||||
else:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class NestedModel(BaseModel):
|
||||
value: int
|
||||
name: str
|
||||
@@ -4799,8 +4793,6 @@ async def test_nested_pydantic_models(version: str) -> None:
|
||||
async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
snapshot: SnapshotAssertion, mocker: MockerFixture, checkpointer_name: str
|
||||
) -> None:
|
||||
from pydantic.v1 import BaseModel, ValidationError
|
||||
|
||||
setup = mocker.Mock()
|
||||
teardown = mocker.Mock()
|
||||
|
||||
@@ -4835,8 +4827,7 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
return sorted(operator.add(x, y))
|
||||
|
||||
class State(BaseModel):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
query: str
|
||||
answer: Optional[str] = None
|
||||
@@ -4992,8 +4983,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
|
||||
async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
|
||||
snapshot: SnapshotAssertion, checkpointer_name: str
|
||||
) -> None:
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
def sorted_add(
|
||||
x: list[str], y: Union[list[str], list[tuple[str, str]]]
|
||||
) -> list[str]:
|
||||
|
||||
@@ -9,6 +9,7 @@ from enum import Enum
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, field_validator, model_validator
|
||||
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.graph.state import StateGraph
|
||||
@@ -45,15 +46,6 @@ def test_is_supported_by_pydantic() -> None:
|
||||
|
||||
assert is_supported_by_pydantic(PydanticModel) is True
|
||||
|
||||
if hasattr(pydantic, "v1"):
|
||||
|
||||
class PydanticModelV1(pydantic.v1.BaseModel):
|
||||
x: int
|
||||
|
||||
assert is_supported_by_pydantic(PydanticModelV1) is False
|
||||
|
||||
assert is_supported_by_pydantic(int) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["v1", "v2"])
|
||||
def test_nested_pydantic_models(version: str) -> None:
|
||||
@@ -84,10 +76,6 @@ def test_nested_pydantic_models(version: str) -> None:
|
||||
conlist,
|
||||
constr,
|
||||
)
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
if BaseModel is BaseModelV1:
|
||||
pytest.skip("Cannot test pydantic v2 using installed version < 2")
|
||||
|
||||
class NestedModel(BaseModel):
|
||||
value: int
|
||||
@@ -314,8 +302,6 @@ def test_nested_pydantic_models(version: str) -> None:
|
||||
|
||||
|
||||
def test_pydantic_state_field_validator():
|
||||
from pydantic import BaseModel, field_validator, model_validator
|
||||
|
||||
class State(BaseModel):
|
||||
name: str
|
||||
text: str = ""
|
||||
@@ -346,32 +332,3 @@ def test_pydantic_state_field_validator():
|
||||
g = builder.compile()
|
||||
res = g.invoke(input_state)
|
||||
assert res["text"] == "Hello, Validated John!"
|
||||
|
||||
|
||||
def test_pydantic_v1_state_root_validator():
|
||||
from pydantic.v1 import BaseModel, root_validator
|
||||
|
||||
class State(BaseModel):
|
||||
name: str
|
||||
text: str = ""
|
||||
only_root: int = 13
|
||||
|
||||
@root_validator(pre=True)
|
||||
@classmethod
|
||||
def validate(cls, values: dict):
|
||||
values["name"] = "Validated " + values["name"]
|
||||
return values | {"only_root": 396}
|
||||
|
||||
input_state = {"name": "John"}
|
||||
|
||||
def process_node(state: State):
|
||||
assert State(**input_state) == state
|
||||
return {"text": "Hello, " + state.name + "!"}
|
||||
|
||||
builder = StateGraph(state_schema=State)
|
||||
builder.add_node("process", process_node)
|
||||
builder.add_edge(START, "process")
|
||||
builder.add_edge("process", END)
|
||||
g = builder.compile()
|
||||
res = g.invoke(input_state)
|
||||
assert res["text"] == "Hello, Validated John!"
|
||||
|
||||
Reference in New Issue
Block a user