9201ef759e
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled
119 lines
4.1 KiB
Python
119 lines
4.1 KiB
Python
"""Static-typing checks for the `pydantic_graph` builder API.
|
|
|
|
This file is not executed by pytest; it's checked by `pyright` and exists to
|
|
catch regressions in the public typing of [`GraphBuilder`][pydantic_graph.GraphBuilder]
|
|
and [`Graph`][pydantic_graph.Graph].
|
|
"""
|
|
|
|
from __future__ import annotations as _annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from typing_extensions import assert_type
|
|
|
|
from pydantic_graph import BaseNode, End, Graph, GraphBuilder, GraphRunContext, StepContext
|
|
|
|
|
|
@dataclass
|
|
class MyState:
|
|
x: int
|
|
|
|
|
|
@dataclass
|
|
class MyDeps:
|
|
y: str
|
|
|
|
|
|
# `GraphBuilder` is generic in (StateT, DepsT, InputT, OutputT), in that order.
|
|
g1 = GraphBuilder(state_type=MyState, deps_type=MyDeps, input_type=str, output_type=int)
|
|
assert_type(g1, GraphBuilder[MyState, MyDeps, str, int])
|
|
|
|
g1_graph = g1.build(validate_graph_structure=False)
|
|
assert_type(g1_graph, Graph[MyState, MyDeps, str, int])
|
|
|
|
|
|
# Defaults: all four type parameters default to `None`.
|
|
g2 = GraphBuilder()
|
|
assert_type(g2, GraphBuilder[None, None, None, None])
|
|
assert_type(g2.build(validate_graph_structure=False), Graph[None, None, None, None])
|
|
|
|
|
|
# Partial parameterization is allowed; unspecified params fall back to `None`.
|
|
g3 = GraphBuilder(input_type=int, output_type=str)
|
|
assert_type(g3, GraphBuilder[None, None, int, str])
|
|
|
|
|
|
async def run_graph_returns_output_type() -> None:
|
|
graph = GraphBuilder(state_type=MyState, deps_type=MyDeps, input_type=int, output_type=str).build(
|
|
validate_graph_structure=False
|
|
)
|
|
result = await graph.run(state=MyState(x=1), deps=MyDeps(y='y'), inputs=5)
|
|
assert_type(result, str)
|
|
|
|
|
|
def run_sync_returns_output_type() -> None:
|
|
graph = GraphBuilder(state_type=MyState, deps_type=MyDeps, input_type=int, output_type=str).build(
|
|
validate_graph_structure=False
|
|
)
|
|
result = graph.run_sync(state=MyState(x=1), deps=MyDeps(y='y'), inputs=5)
|
|
assert_type(result, str)
|
|
|
|
|
|
async def run_graph_rejects_wrong_state() -> None:
|
|
graph = GraphBuilder(state_type=MyState, output_type=str).build(validate_graph_structure=False)
|
|
# state=... must match `state_type`; passing a `str` where `MyState` is expected should error.
|
|
await graph.run(state='not a MyState', inputs=None) # type: ignore[arg-type]
|
|
|
|
|
|
async def run_graph_rejects_wrong_deps() -> None:
|
|
graph = GraphBuilder(deps_type=MyDeps, output_type=str).build(validate_graph_structure=False)
|
|
await graph.run(deps=42, inputs=None) # type: ignore[arg-type]
|
|
|
|
|
|
async def run_graph_rejects_wrong_inputs() -> None:
|
|
graph = GraphBuilder(input_type=int, output_type=str).build(validate_graph_structure=False)
|
|
await graph.run(inputs='not an int') # type: ignore[arg-type]
|
|
|
|
|
|
# `BaseNode` subclasses parameterized by state/deps/run-end types still type-check.
|
|
@dataclass
|
|
class StartingNode(BaseNode[MyState, MyDeps, int]):
|
|
async def run(self, ctx: GraphRunContext[MyState, MyDeps]) -> SecondNode:
|
|
assert ctx.state.x == 1
|
|
assert ctx.deps.y == 'y'
|
|
return SecondNode()
|
|
|
|
|
|
@dataclass
|
|
class SecondNode(BaseNode[MyState, MyDeps, int]):
|
|
async def run(self, ctx: GraphRunContext[MyState, MyDeps]) -> End[int]:
|
|
return End(42)
|
|
|
|
|
|
# A `BaseNode` typed for one (State, Deps) pair should be assignable to the
|
|
# corresponding `BaseNode[...]` annotation.
|
|
def use_starting_node(node: BaseNode[MyState, MyDeps, int]) -> None:
|
|
print(node)
|
|
|
|
|
|
use_starting_node(StartingNode())
|
|
|
|
|
|
# Step functions are typed via `StepContext` and their input/output types
|
|
# flow through the graph builder.
|
|
async def step_returns_output_type() -> None:
|
|
g = GraphBuilder(state_type=MyState, deps_type=MyDeps, input_type=int, output_type=str)
|
|
|
|
@g.step
|
|
async def to_str(ctx: StepContext[MyState, MyDeps, int]) -> str:
|
|
assert_type(ctx.inputs, int)
|
|
assert_type(ctx.state, MyState)
|
|
assert_type(ctx.deps, MyDeps)
|
|
return str(ctx.inputs)
|
|
|
|
g.add(g.edge_from(g.start_node).to(to_str), g.edge_from(to_str).to(g.end_node))
|
|
graph = g.build()
|
|
assert_type(graph, Graph[MyState, MyDeps, int, str])
|
|
result = await graph.run(state=MyState(x=1), deps=MyDeps(y='y'), inputs=5)
|
|
assert_type(result, str)
|