Skip to content

Commit 6eae653

Browse files
committed
Fix test coverage
1 parent a3c9a59 commit 6eae653

File tree

2 files changed

+28
-192
lines changed

2 files changed

+28
-192
lines changed

pydantic_ai_slim/pydantic_ai/toolsets/prefixed.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def tool_defs(self) -> list[ToolDefinition]:
2626
return [replace(tool_def, name=self._prefixed_tool_name(tool_def.name)) for tool_def in super().tool_defs]
2727

2828
def _max_retries_for_tool(self, name: str) -> int:
29-
return super()._max_retries_for_tool(self._unprefixed_tool_name(name))
29+
return super()._max_retries_for_tool(self._unprefixed_tool_name(name)) # pragma: no cover
3030

3131
async def call_tool(self, call: ToolCallPart, ctx: RunContext[AgentDepsT], allow_partial: bool = False) -> Any:
3232
call = replace(call, tool_name=self._unprefixed_tool_name(call.tool_name))

tests/test_toolset.py renamed to tests/test_toolsets.py

Lines changed: 27 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async def test_prepared_toolset_user_error_add_new_tools():
145145
@base_toolset.tool
146146
def add(a: int, b: int) -> int:
147147
"""Add two numbers"""
148-
return a + b
148+
return a + b # pragma: no cover
149149

150150
async def prepare_add_new_tool(ctx: RunContext[None], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
151151
# Try to add a new tool that wasn't in the original set
@@ -175,7 +175,7 @@ async def test_prepared_toolset_user_error_change_tool_names():
175175
@base_toolset.tool
176176
def add(a: int, b: int) -> int:
177177
"""Add two numbers"""
178-
return a + b
178+
return a + b # pragma: no cover
179179

180180
async def prepare_change_names(ctx: RunContext[None], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
181181
# Try to change the name of an existing tool
@@ -193,126 +193,6 @@ async def prepare_change_names(ctx: RunContext[None], tool_defs: list[ToolDefini
193193
await prepared_toolset.prepare_for_run(context)
194194

195195

196-
async def test_prepared_toolset_allows_removing_tools():
197-
"""Test that PreparedToolset allows removing tools from the original set."""
198-
context = build_run_context(None)
199-
base_toolset = FunctionToolset[None]()
200-
201-
@base_toolset.tool
202-
def add(a: int, b: int) -> int:
203-
"""Add two numbers"""
204-
return a + b
205-
206-
@base_toolset.tool
207-
def subtract(a: int, b: int) -> int:
208-
"""Subtract two numbers"""
209-
return a - b
210-
211-
@base_toolset.tool
212-
def multiply(a: int, b: int) -> int:
213-
"""Multiply two numbers"""
214-
return a * b
215-
216-
async def prepare_remove_tools(ctx: RunContext[None], tool_defs: list[ToolDefinition]) -> list[ToolDefinition]:
217-
# Remove the 'subtract' tool, keep 'add' and 'multiply'
218-
return [tool_def for tool_def in tool_defs if tool_def.name != 'subtract']
219-
220-
prepared_toolset = PreparedToolset(base_toolset, prepare_remove_tools)
221-
222-
# This should not raise an error
223-
run_toolset = await prepared_toolset.prepare_for_run(context)
224-
225-
# Verify that only 'add' and 'multiply' tools are available
226-
assert set(run_toolset.tool_names) == {'add', 'multiply'}
227-
assert len(run_toolset.tool_defs) == 2
228-
229-
# Verify that the tools still work
230-
assert await run_toolset.call_tool(ToolCallPart(tool_name='add', args={'a': 5, 'b': 3}), context) == 8
231-
assert await run_toolset.call_tool(ToolCallPart(tool_name='multiply', args={'a': 4, 'b': 2}), context) == 8
232-
233-
234-
async def test_prefixed_toolset_tool_defs():
235-
"""Test that PrefixedToolset correctly prefixes tool definitions."""
236-
base_toolset = FunctionToolset[None]()
237-
238-
@base_toolset.tool
239-
def add(a: int, b: int) -> int:
240-
"""Add two numbers"""
241-
return a + b
242-
243-
@base_toolset.tool
244-
def subtract(a: int, b: int) -> int:
245-
"""Subtract two numbers"""
246-
return a - b
247-
248-
prefixed_toolset = PrefixedToolset(base_toolset, 'math')
249-
250-
# Check that tool names are prefixed
251-
assert prefixed_toolset.tool_names == ['math_add', 'math_subtract']
252-
253-
# Check that tool definitions have prefixed names
254-
tool_defs = prefixed_toolset.tool_defs
255-
assert len(tool_defs) == 2
256-
257-
add_def = next(td for td in tool_defs if td.name == 'math_add')
258-
subtract_def = next(td for td in tool_defs if td.name == 'math_subtract')
259-
260-
assert add_def.name == 'math_add'
261-
assert add_def.description == 'Add two numbers'
262-
assert subtract_def.name == 'math_subtract'
263-
assert subtract_def.description == 'Subtract two numbers'
264-
265-
266-
async def test_prefixed_toolsetcall_tools():
267-
"""Test that PrefixedToolset correctly calls tools with prefixed names."""
268-
context = build_run_context(None)
269-
base_toolset = FunctionToolset[None]()
270-
271-
@base_toolset.tool
272-
def add(a: int, b: int) -> int:
273-
"""Add two numbers"""
274-
return a + b
275-
276-
@base_toolset.tool
277-
def multiply(a: int, b: int) -> int:
278-
"""Multiply two numbers"""
279-
return a * b
280-
281-
prefixed_toolset = PrefixedToolset(base_toolset, 'calc')
282-
283-
# Test calling tools with prefixed names
284-
result = await prefixed_toolset.call_tool(ToolCallPart(tool_name='calc_add', args={'a': 5, 'b': 3}), context)
285-
assert result == 8
286-
287-
result = await prefixed_toolset.call_tool(ToolCallPart(tool_name='calc_multiply', args={'a': 4, 'b': 2}), context)
288-
assert result == 8
289-
290-
291-
async def test_prefixed_toolset_prepare_for_run():
292-
"""Test that PrefixedToolset correctly prepares for run with prefixed tools."""
293-
context = build_run_context(None)
294-
base_toolset = FunctionToolset[None]()
295-
296-
@base_toolset.tool
297-
def add(a: int, b: int) -> int:
298-
"""Add two numbers"""
299-
return a + b
300-
301-
prefixed_toolset = PrefixedToolset(base_toolset, 'test')
302-
303-
# Prepare for run
304-
run_toolset = await prefixed_toolset.prepare_for_run(context)
305-
306-
# Verify that the run toolset has prefixed tools
307-
assert run_toolset.tool_names == ['test_add']
308-
assert len(run_toolset.tool_defs) == 1
309-
assert run_toolset.tool_defs[0].name == 'test_add'
310-
311-
# Verify that the tool still works
312-
result = await run_toolset.call_tool(ToolCallPart(tool_name='test_add', args={'a': 10, 'b': 5}), context)
313-
assert result == 15
314-
315-
316196
async def test_prefixed_toolset_error_invalid_prefix():
317197
"""Test that PrefixedToolset raises ValueError for tool names that don't start with the prefix."""
318198
context = build_run_context(None)
@@ -321,46 +201,14 @@ async def test_prefixed_toolset_error_invalid_prefix():
321201
@base_toolset.tool
322202
def add(a: int, b: int) -> int:
323203
"""Add two numbers"""
324-
return a + b
204+
return a + b # pragma: no cover
325205

326206
prefixed_toolset = PrefixedToolset(base_toolset, 'math')
327207

328208
# Test calling with wrong prefix
329209
with pytest.raises(ValueError, match="Tool name 'wrong_add' does not start with prefix 'math_'"):
330210
await prefixed_toolset.call_tool(ToolCallPart(tool_name='wrong_add', args={'a': 1, 'b': 2}), context)
331211

332-
# Test calling with no prefix
333-
with pytest.raises(ValueError, match="Tool name 'add' does not start with prefix 'math_'"):
334-
await prefixed_toolset.call_tool(ToolCallPart(tool_name='add', args={'a': 1, 'b': 2}), context)
335-
336-
# Test calling with partial prefix
337-
with pytest.raises(ValueError, match="Tool name 'mat_add' does not start with prefix 'math_'"):
338-
await prefixed_toolset.call_tool(ToolCallPart(tool_name='mat_add', args={'a': 1, 'b': 2}), context)
339-
340-
341-
async def test_prefixed_toolset_empty_prefix():
342-
"""Test that PrefixedToolset works correctly with an empty prefix."""
343-
context = build_run_context(None)
344-
base_toolset = FunctionToolset[None]()
345-
346-
@base_toolset.tool
347-
def add(a: int, b: int) -> int:
348-
"""Add two numbers"""
349-
return a + b
350-
351-
prefixed_toolset = PrefixedToolset(base_toolset, '')
352-
353-
# Check that tool names have empty prefix (just underscore)
354-
assert prefixed_toolset.tool_names == ['_add']
355-
356-
# Test calling the tool
357-
result = await prefixed_toolset.call_tool(ToolCallPart(tool_name='_add', args={'a': 3, 'b': 4}), context)
358-
assert result == 7
359-
360-
# Test error for wrong name
361-
with pytest.raises(ValueError, match="Tool name 'add' does not start with prefix '_'"):
362-
await prefixed_toolset.call_tool(ToolCallPart(tool_name='add', args={'a': 1, 'b': 2}), context)
363-
364212

365213
async def test_comprehensive_toolset_composition():
366214
"""Test that all toolsets can be composed together and work correctly."""
@@ -381,12 +229,12 @@ def add(a: int, b: int) -> int:
381229
@math_toolset.tool
382230
def subtract(a: int, b: int) -> int:
383231
"""Subtract two numbers"""
384-
return a - b
232+
return a - b # pragma: no cover
385233

386234
@math_toolset.tool
387235
def multiply(a: int, b: int) -> int:
388236
"""Multiply two numbers"""
389-
return a * b
237+
return a * b # pragma: no cover
390238

391239
# Create second FunctionToolset with string operations
392240
string_toolset = FunctionToolset[TestDeps]()
@@ -399,31 +247,20 @@ def concat(s1: str, s2: str) -> str:
399247
@string_toolset.tool
400248
def uppercase(text: str) -> str:
401249
"""Convert text to uppercase"""
402-
return text.upper()
250+
return text.upper() # pragma: no cover
403251

404252
@string_toolset.tool
405253
def reverse(text: str) -> str:
406254
"""Reverse a string"""
407-
return text[::-1]
255+
return text[::-1] # pragma: no cover
408256

409257
# Create third FunctionToolset with advanced operations
410258
advanced_toolset = FunctionToolset[TestDeps]()
411259

412260
@advanced_toolset.tool
413261
def power(base: int, exponent: int) -> int:
414262
"""Calculate base raised to the power of exponent"""
415-
return base**exponent
416-
417-
@advanced_toolset.tool
418-
def factorial(n: int) -> int:
419-
"""Calculate factorial of n"""
420-
421-
def _fact(x: int) -> int:
422-
if x <= 1:
423-
return 1
424-
return x * _fact(x - 1)
425-
426-
return _fact(n)
263+
return base**exponent # pragma: no cover
427264

428265
# Step 1: Prefix each FunctionToolset individually
429266
prefixed_math = PrefixedToolset(math_toolset, 'math')
@@ -501,16 +338,6 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
501338
'type': 'object',
502339
},
503340
),
504-
ToolDefinition(
505-
name='adv_factorial',
506-
description='Calculate factorial of n (role: user)',
507-
parameters_json_schema={
508-
'additionalProperties': False,
509-
'properties': {'n': {'type': 'integer'}},
510-
'required': ['n'],
511-
'type': 'object',
512-
},
513-
),
514341
]
515342
)
516343
# Call a tool and check result
@@ -593,16 +420,6 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
593420
'type': 'object',
594421
},
595422
),
596-
ToolDefinition(
597-
name='adv_factorial',
598-
description='Calculate factorial of n (role: admin)',
599-
parameters_json_schema={
600-
'additionalProperties': False,
601-
'properties': {'n': {'type': 'integer'}},
602-
'required': ['n'],
603-
'type': 'object',
604-
},
605-
),
606423
]
607424
)
608425
result = await admin_final_toolset.call_tool(
@@ -656,3 +473,22 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
656473
toolset_once = await prepared_toolset.prepare_for_run(ctx2)
657474
toolset_twice = await (await prepared_toolset.prepare_for_run(ctx1)).prepare_for_run(ctx2)
658475
assert toolset_once == toolset_twice
476+
477+
478+
async def test_context_manager():
479+
try:
480+
from pydantic_ai.mcp import MCPServerStdio
481+
except ImportError:
482+
return
483+
484+
server1 = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
485+
server2 = MCPServerStdio('python', ['-m', 'tests.mcp_server'])
486+
toolset = CombinedToolset([server1, PrefixedToolset(server2, 'prefix')])
487+
488+
async with toolset:
489+
assert server1.is_running
490+
assert server2.is_running
491+
492+
async with toolset:
493+
assert server1.is_running
494+
assert server2.is_running

0 commit comments

Comments
 (0)