Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions pydantic_ai_slim/pydantic_ai/_json_schema.py
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: items: false skipped by falsy walrus check in _handle_array, but functionally correct

At line 154, if items := schema.get('items'): will skip items: false because False is falsy. This means _handle(False) is never called for this case. However, since _handle(False) returns False immediately (line 86-87), the end result is identical — the value is preserved as-is. This is consistent with how additionalProperties: false is handled in _handle_object (line 137, explicit isinstance(bool) check). The inconsistency in approach is worth noting but doesn't cause incorrect behavior.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Downstream walk() overrides assume dict return type

The OpenAIJsonSchemaTransformer.walk() override at pydantic_ai_slim/pydantic_ai/profiles/openai.py:230 calls result.pop('$ref', None) and result.update(...) on the return value of super().walk(). The AnthropicJsonSchemaTransformer.walk() at pydantic_ai_slim/pydantic_ai/providers/anthropic.py:126 passes the result to transform_schema(). Both would crash if walk() returned a boolean. In practice, these transformers only process Pydantic-generated schemas (which always have object or $ref roots), so the boolean case is currently unreachable through these code paths. However, the type contract of walk() has silently changed with this PR — the return type annotation still says JsonSchema (i.e., dict[str, Any]) but the method can now return bool. This mismatch could cause issues if the API is used more broadly in the future.

(Refers to line 57)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ def walk(self) -> JsonSchema:
return handled

def _handle(self, schema: JsonSchema) -> JsonSchema:
# JSON Schema allows boolean values: true = accept anything, false = accept nothing
if isinstance(schema, bool):
return self.transform(schema)

nested_refs = 0
if self.prefer_inlined_defs:
while ref := schema.get('$ref'):
Expand Down
34 changes: 34 additions & 0 deletions tests/test_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,37 @@ def transform(self, schema: dict[str, Any]) -> dict[str, Any]:

# Verify the result is correct
assert result == original_schema_copy


def test_boolean_schema_nodes():
"""Test that boolean JSON Schema nodes (true/false) are handled correctly.

In JSON Schema, `true` means 'accept anything' (equivalent to {})
and `false` means 'accept nothing' (equivalent to {'not': {}}).
See: https://github.com/pydantic/pydantic-ai/issues/4771
"""

class PassthroughTransformer(JsonSchemaTransformer):
def transform(self, schema: dict[str, Any]) -> dict[str, Any]:
return schema

schema = {
'type': 'object',
'properties': {
'fields': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'value': True,
'denied': False,
},
},
},
},
}

result = PassthroughTransformer(schema).walk()
# Boolean nodes should be preserved
assert result['properties']['fields']['items']['properties']['value'] is True
assert result['properties']['fields']['items']['properties']['denied'] is False
Loading