Skip to content

Commit 2b33263

Browse files
author
Yashwant Bezawada
committed
Address Codex review: Recursively process additionalProperties
Fixes the issue identified in Codex review where Dict[str, Decimal] would still fail because additionalProperties schemas were not being recursively processed. The previous fix stripped unsupported keywords from the top-level schema and recursively processed properties, items, anyOf, and allOf. However, it missed additionalProperties which Pydantic uses for typed dictionaries like Dict[str, Decimal]. Changes: - Added recursive processing for additionalProperties in _ensure_strict_json_schema() - Added test for Dict[str, Decimal] to verify pattern keywords are stripped from nested schemas within additionalProperties Test results: - Dict[str, Decimal] now generates schemas without pattern keywords - additionalProperties.anyOf properly sanitized - All constrained types work in dictionary values
1 parent fdde518 commit 2b33263

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

src/openai/lib/_pydantic.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ def _ensure_strict_json_schema(
6666
if is_dict(items):
6767
json_schema["items"] = _ensure_strict_json_schema(items, path=(*path, "items"), root=root)
6868

69+
# typed dictionaries
70+
# { 'type': 'object', 'additionalProperties': {...} }
71+
# Note: additionalProperties can be boolean (true/false) or a schema dict
72+
additional_properties = json_schema.get("additionalProperties")
73+
if is_dict(additional_properties):
74+
json_schema["additionalProperties"] = _ensure_strict_json_schema(
75+
additional_properties, path=(*path, "additionalProperties"), root=root
76+
)
77+
6978
# unions
7079
any_of = json_schema.get("anyOf")
7180
if is_list(any_of):

tests/lib/test_pydantic.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,76 @@ def test_decimal_field_strips_pattern() -> None:
480480
"additionalProperties": False,
481481
}
482482
)
483+
484+
485+
class ProductPricing(BaseModel):
486+
"""Test model with Dict[str, Decimal] to verify pattern is stripped from additionalProperties"""
487+
prices: dict[str, Decimal] = Field(description="Product prices by region")
488+
product_name: str = Field(description="The product name")
489+
490+
491+
def test_dict_decimal_strips_pattern_in_additional_properties() -> None:
492+
"""
493+
Test that Dict[str, Decimal] fields strip pattern from additionalProperties.
494+
495+
When Pydantic generates schemas for typed dictionaries (Dict[str, Decimal]),
496+
it uses additionalProperties with a Decimal schema that includes a regex
497+
pattern. This test verifies that pattern keywords are stripped from nested
498+
schemas within additionalProperties.
499+
500+
Addresses Codex review feedback on PR #2733
501+
"""
502+
if not PYDANTIC_V1:
503+
schema = to_strict_json_schema(ProductPricing)
504+
505+
# Verify the schema structure exists
506+
assert "properties" in schema
507+
assert "prices" in schema["properties"]
508+
509+
# Get the prices field schema
510+
prices_schema = schema["properties"]["prices"]
511+
512+
# Should be an object with additionalProperties
513+
assert prices_schema.get("type") == "object"
514+
assert "additionalProperties" in prices_schema
515+
516+
# Get the additionalProperties schema (Decimal schema)
517+
add_props = prices_schema["additionalProperties"]
518+
assert "anyOf" in add_props
519+
520+
# Check all variants in anyOf for 'pattern' keyword
521+
# Pattern should NOT be present after our fix
522+
for variant in add_props["anyOf"]:
523+
assert "pattern" not in variant, (
524+
"Pattern keyword should be stripped from additionalProperties Decimal schema. "
525+
"Found pattern in variant: " + str(variant)
526+
)
527+
528+
# Verify the full schema matches expected structure
529+
assert schema == snapshot(
530+
{
531+
"description": "Test model with Dict[str, Decimal] to verify pattern is stripped from additionalProperties",
532+
"properties": {
533+
"prices": {
534+
"additionalProperties": {
535+
"anyOf": [
536+
{"type": "number"},
537+
{"type": "string"},
538+
]
539+
},
540+
"description": "Product prices by region",
541+
"title": "Prices",
542+
"type": "object",
543+
},
544+
"product_name": {
545+
"description": "The product name",
546+
"title": "Product Name",
547+
"type": "string",
548+
},
549+
},
550+
"required": ["prices", "product_name"],
551+
"title": "ProductPricing",
552+
"type": "object",
553+
"additionalProperties": False,
554+
}
555+
)

0 commit comments

Comments
 (0)