Skip to content

change format_as_xml defaults #2228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions pydantic_ai_slim/pydantic_ai/format_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@

def format_as_xml(
obj: Any,
root_tag: str = 'examples',
item_tag: str = 'example',
include_root_tag: bool = True,
root_tag: str | None = None,
item_tag: str = 'item',
none_str: str = 'null',
indent: str | None = ' ',
) -> str:
Expand All @@ -32,8 +31,6 @@ def format_as_xml(
root_tag: Outer tag to wrap the XML in, use `None` to omit the outer tag.
item_tag: Tag to use for each item in an iterable (e.g. list), this is overridden by the class name
for dataclasses and Pydantic models.
include_root_tag: Whether to include the root tag in the output
(The root tag is always included if it includes a body - e.g. when the input is a simple value).
none_str: String to use for `None` values.
indent: Indentation string to use for pretty printing.

Expand All @@ -55,7 +52,7 @@ def format_as_xml(
```
"""
el = _ToXml(item_tag=item_tag, none_str=none_str).to_xml(obj, root_tag)
if not include_root_tag and el.text is None:
if root_tag is None and el.text is None:
join = '' if indent is None else '\n'
return join.join(_rootless_xml_elements(el, indent))
else:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ async def model_logic( # noqa: C901
)
elif m.content.startswith('Write a list of 5 very rude things that I might say'):
raise UnexpectedModelBehavior('Safety settings triggered', body='<safety settings details>')
elif m.content.startswith('<examples>\n <user>'):
elif m.content.startswith('<user>\n <name>John Doe</name>'):
return ModelResponse(
parts=[ToolCallPart(tool_name='final_result_EmailOk', args={}, tool_call_id='pyd_ai_tool_call_id')]
)
Expand Down
48 changes: 23 additions & 25 deletions tests/test_format_as_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,35 +123,35 @@ class ExamplePydanticModel(BaseModel):
),
],
)
def test(input_obj: Any, output: str):
assert format_as_xml(input_obj) == output
def test_root_tag(input_obj: Any, output: str):
assert format_as_xml(input_obj, root_tag='examples', item_tag='example') == output


@pytest.mark.parametrize(
'input_obj,output',
[
pytest.param('a string', snapshot('<examples>a string</examples>'), id='string'),
pytest.param('a <ex>foo</ex>', snapshot('<examples>a &lt;ex&gt;foo&lt;/ex&gt;</examples>'), id='string'),
pytest.param(42, snapshot('<examples>42</examples>'), id='int'),
pytest.param('a string', snapshot('<item>a string</item>'), id='string'),
pytest.param('a <ex>foo</ex>', snapshot('<item>a &lt;ex&gt;foo&lt;/ex&gt;</item>'), id='string'),
pytest.param(42, snapshot('<item>42</item>'), id='int'),
pytest.param(
[1, 2, 3],
snapshot("""\
<example>1</example>
<example>2</example>
<example>3</example>\
<item>1</item>
<item>2</item>
<item>3</item>\
"""),
id='list[int]',
),
pytest.param(
[[1, 2], [3]],
snapshot("""\
<example>
<example>1</example>
<example>2</example>
</example>
<example>
<example>3</example>
</example>\
<item>
<item>1</item>
<item>2</item>
</item>
<item>
<item>3</item>
</item>\
"""),
id='list[list[int]]',
),
Expand All @@ -166,24 +166,22 @@ def test(input_obj: Any, output: str):
pytest.param(
[datetime(2025, 1, 1, 12, 13), date(2025, 1, 2)],
snapshot("""\
<example>2025-01-01T12:13:00</example>
<example>2025-01-02</example>\
<item>2025-01-01T12:13:00</item>
<item>2025-01-02</item>\
"""),
id='list[date]',
),
],
)
def test_no_root(input_obj: Any, output: str):
assert format_as_xml(input_obj, include_root_tag=False) == output
assert format_as_xml(input_obj) == output


def test_no_indent():
assert format_as_xml([1, 2, 3], indent=None) == snapshot(
'<examples><example>1</example><example>2</example><example>3</example></examples>'
)
assert format_as_xml([1, 2, 3], indent=None, include_root_tag=False) == snapshot(
'<example>1</example><example>2</example><example>3</example>'
assert format_as_xml([1, 2, 3], indent=None, root_tag='example') == snapshot(
'<example><item>1</item><item>2</item><item>3</item></example>'
)
assert format_as_xml([1, 2, 3], indent=None) == snapshot('<item>1</item><item>2</item><item>3</item>')


def test_invalid_value():
Expand All @@ -197,8 +195,8 @@ def test_invalid_key():


def test_set():
assert '<example>1</example>' in format_as_xml({1, 2, 3})
assert '<example>1</example>' in format_as_xml({1, 2, 3}, item_tag='example')


def test_custom_null():
assert format_as_xml(None, none_str='nil') == snapshot('<examples>nil</examples>')
assert format_as_xml(None, none_str='nil') == snapshot('<item>nil</item>')