Skip to content
Open
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
2 changes: 0 additions & 2 deletions .inline-snapshot/external/.gitignore

This file was deleted.

31 changes: 29 additions & 2 deletions src/anthropic/lib/_parse/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
}


TYPE_SPECIFIC_KEYWORDS: dict[str, set[str]] = {
"object": {"properties", "additionalProperties", "required", "patternProperties", "minProperties", "maxProperties"},
"array": {"items", "minItems", "maxItems", "uniqueItems", "contains"},
"string": {"format", "pattern", "minLength", "maxLength"},
"integer": {"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"},
"number": {"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"},
"boolean": set(),
"null": set(),
}


def get_transformed_string(
schema: dict[str, Any],
) -> dict[str, Any]:
Expand Down Expand Up @@ -96,7 +107,7 @@ def transform_schema(
strict_schema["$ref"] = ref
return strict_schema

type_: Optional[SupportedTypes] = json_schema.pop("type", None)
type_: Optional[SupportedTypes | list[SupportedTypes]] = json_schema.pop("type", None)
any_of = json_schema.pop("anyOf", None)
one_of = json_schema.pop("oneOf", None)
all_of = json_schema.pop("allOf", None)
Expand All @@ -107,6 +118,22 @@ def transform_schema(
strict_schema["anyOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in one_of]
elif is_list(all_of):
strict_schema["allOf"] = [transform_schema(cast("dict[str, Any]", variant)) for variant in all_of]
elif is_list(type_):
variants: list[dict[str, Any]] = []
extracted_keywords: dict[str, Any] = {}
for t in type_:
for kw in TYPE_SPECIFIC_KEYWORDS.get(t, set()):
if kw in json_schema:
extracted_keywords[kw] = json_schema.pop(kw)

for t in type_:
branch: dict[str, Any] = {"type": t}
for kw in TYPE_SPECIFIC_KEYWORDS.get(t, set()):
if kw in extracted_keywords:
branch[kw] = extracted_keywords[kw]
variants.append(transform_schema(branch))

strict_schema["anyOf"] = variants
else:
if type_ is None:
raise ValueError("Schema must have a 'type', 'anyOf', 'oneOf', or 'allOf' field.")
Expand Down Expand Up @@ -155,7 +182,7 @@ def transform_schema(
# add it back so its treated as an extra property and appended to the description
json_schema["minItems"] = min_items

elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None:
elif type_ == "boolean" or type_ == "integer" or type_ == "number" or type_ == "null" or type_ is None or is_list(type_):
pass
else:
assert_never(type_)
Expand Down
104 changes: 104 additions & 0 deletions tests/lib/_parse/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,107 @@ def test_original_schema_not_mutated():
transform_schema(original_schema)

assert original_schema == original_schema_backup


def test_type_array():
schema = {
"type": ["string", "null"],
"description": "Optional text",
}
result = transform_schema(schema)
assert result == {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Optional text",
}


def test_type_array_in_object():
schema = {
"type": "object",
"properties": {
"name": {
"type": ["string", "null"],
"description": "Optional name",
}
},
"required": ["name"],
}
result = transform_schema(schema)
assert result == {
"type": "object",
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Optional name",
}
},
"required": ["name"],
"additionalProperties": False,
}


def test_type_array_with_items():
schema = {
"type": ["array", "null"],
"items": {"type": "string"},
"description": "Optional list of strings",
}
result = transform_schema(schema)
assert result == {
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
"description": "Optional list of strings",
}


def test_type_array_with_object_properties():
schema = {
"type": ["object", "null"],
"properties": {
"title": {"type": "string"},
},
"required": ["title"],
}
result = transform_schema(schema)
assert result == {
"anyOf": [
{
"type": "object",
"properties": {"title": {"type": "string"}},
"required": ["title"],
"additionalProperties": False,
},
{"type": "null"},
],
}


def test_type_array_nested_in_object_with_items():
schema = {
"type": "object",
"properties": {
"tags": {
"type": ["array", "null"],
"items": {"type": "string"},
}
},
"required": ["tags"],
}
result = transform_schema(schema)
assert result == {
"type": "object",
"properties": {
"tags": {
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
}
},
"required": ["tags"],
"additionalProperties": False,
}