Skip to content

Commit 17555b4

Browse files
committed
Fix coverage
1 parent f49b999 commit 17555b4

File tree

4 files changed

+37
-28
lines changed

4 files changed

+37
-28
lines changed

pydantic_graph/pydantic_graph/beta/graph.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
from __future__ import annotations as _annotations
99

10-
import inspect
11-
import types
1210
import uuid
1311
from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Iterable, Sequence
1412
from contextlib import AbstractContextManager, ExitStack, asynccontextmanager, contextmanager
@@ -188,7 +186,7 @@ async def run(
188186
"""
189187
if infer_name and self.name is None:
190188
inferred_name = infer_obj_name(self, depth=2)
191-
if inferred_name is not None:
189+
if inferred_name is not None: # pragma: no branch
192190
self.name = inferred_name
193191

194192
async with self.iter(state=state, deps=deps, inputs=inputs, span=span, infer_name=False) as graph_run:
@@ -229,9 +227,9 @@ async def iter(
229227
A GraphRun instance that can be iterated for step-by-step execution
230228
"""
231229
if infer_name and self.name is None:
232-
# f_back because `asynccontextmanager` adds one frame
233-
if frame := inspect.currentframe(): # pragma: no branch
234-
self._infer_name(frame.f_back)
230+
inferred_name = infer_obj_name(self, depth=3) # depth=3 because asynccontextmanager adds one
231+
if inferred_name is not None: # pragma: no branch
232+
self.name = inferred_name
235233

236234
with ExitStack() as stack:
237235
entered_span: AbstractSpan | None = None
@@ -277,26 +275,6 @@ def __str__(self) -> str:
277275
"""
278276
return self.render()
279277

280-
def _infer_name(self, function_frame: types.FrameType | None) -> None:
281-
"""Infer the agent name from the call frame.
282-
283-
Usage should be `self._infer_name(inspect.currentframe())`.
284-
285-
Copied from `Agent`.
286-
"""
287-
assert self.name is None, 'Name already set'
288-
if function_frame is not None and (parent_frame := function_frame.f_back): # pragma: no branch
289-
for name, item in parent_frame.f_locals.items():
290-
if item is self:
291-
self.name = name
292-
return
293-
if parent_frame.f_locals != parent_frame.f_globals: # pragma: no branch
294-
# if we couldn't find the agent in locals and globals are a different dict, try globals
295-
for name, item in parent_frame.f_globals.items(): # pragma: no branch
296-
if item is self:
297-
self.name = name
298-
return
299-
300278

301279
@dataclass
302280
class GraphTask:

pydantic_graph/pydantic_graph/beta/graph_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ def build(self, validate_graph_structure: bool = True) -> Graph[StateT, DepsT, G
641641
642642
Args:
643643
validate_graph_structure: whether to perform validation of the graph structure
644-
See the docstring of `_validate_graph_structure` below for more details.
644+
See the docstring of _validate_graph_structure below for more details.
645645
646646
Returns:
647647
A complete Graph instance ready for execution

tests/graph/beta/test_graph_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ async def test_validation_unreachable_nodes():
411411

412412
@g.step
413413
async def reachable_step(ctx: StepContext[None, None, None]) -> int:
414-
return 10
414+
return 10 # pragma: no cover
415415

416416
@g.step
417417
async def unreachable_step(ctx: StepContext[None, None, int]) -> int:

tests/graph/beta/test_parent_forks.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,34 @@ def test_parent_fork_explicit_fail_with_cycle():
271271
match="There is a cycle in the graph passing through 'J' that does not include 'F'. Parent forks of a join must be a part of any cycles involving that join.",
272272
):
273273
finder.find_parent_fork(join_id, parent_fork_id='F')
274+
275+
276+
def test_parent_fork_ancestor_fork_with_cycle():
277+
"""Test early return when ancestor fork has cycle but descendant fork is valid.
278+
279+
This test covers the case where:
280+
- F2 is a valid parent fork (part of the cycle, so skipped during backwards walk)
281+
- F1 is an ancestor of F2 but invalid (cycle to J bypasses F1)
282+
- Should return F2 as the parent fork when walking up the dominator chain
283+
"""
284+
join_id = 'J'
285+
nodes = {'start', 'F1', 'F2', 'A', 'J', 'end'}
286+
start_ids = {'start'}
287+
fork_ids = {'F1', 'F2'}
288+
# J -> F2 creates a cycle, but F2 is part of it so it's valid.
289+
# F1 is an ancestor but the cycle bypasses it.
290+
edges = {
291+
'start': ['F1'],
292+
'F1': ['F2'],
293+
'F2': ['A'],
294+
'A': ['J'],
295+
'J': ['F2', 'end'], # Cycle back to F2
296+
}
297+
298+
finder = ParentForkFinder(nodes, start_ids, fork_ids, edges)
299+
parent_fork = finder.find_parent_fork(join_id)
300+
301+
# Should find F2 as valid parent, then hit F1 which has a cycle,
302+
# and return F2 (hitting the early return path with assert False)
303+
assert parent_fork is not None
304+
assert parent_fork.fork_id == 'F2'

0 commit comments

Comments
 (0)