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
10 changes: 8 additions & 2 deletions guidance/library/_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ class GenerateJsonSchemaSafe(pydantic.json_schema.GenerateJsonSchema):

def generate_inner(self, schema):
if schema["type"] == "dict":
key_type = schema["keys_schema"]["type"]
if key_type != "str":
# A dict with no key type (bare ``dict``, ``Dict[Any, ...]``) has an
# ``"any"`` keys_schema. In JSON that maps to an unconstrained object
# (JSON object keys are always strings), which is a valid, supported
# schema, so it must not be rejected as "non-string keys". Only key
# types that genuinely can't be represented as JSON strings (``int``,
# ``float``, ...) are rejected.
key_type = schema.get("keys_schema", {}).get("type", "any")
if key_type not in ("str", "any"):
raise TypeError(f"JSON does not support non-string keys, got type {key_type}")
return super().generate_inner(schema)

Expand Down
22 changes: 22 additions & 0 deletions tests/unit/library/test_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,28 @@ def test_prevent_non_string_keys(self):
generate_and_check({1: 2}, model)
assert exc_info.value.args[0] == "JSON does not support non-string keys, got type int"

def test_any_keys_allowed(self):
"""
A dict with no key type (bare ``dict``, ``Dict[Any, ...]``) maps to an
unconstrained JSON object and must not be rejected as "non-string keys":
JSON object keys are always strings, and the resulting ``{"type":
"object"}`` schema is supported by the backend.
"""
for model in (
pydantic.TypeAdapter(dict),
pydantic.TypeAdapter(dict[str, Any]),
pydantic.TypeAdapter(dict[Any, Any]),
):
generate_and_check({"a": 1, "b": 2}, model)

def test_any_keys_allowed_as_model_field(self):
# The common "arbitrary JSON metadata" pattern: a bare dict field.
class Config(pydantic.BaseModel):
name: str
metadata: dict

generate_and_check({"name": "x", "metadata": {"k": 1}}, Config)


class TestComposite:
class Simple(pydantic.BaseModel):
Expand Down