[Bug]: MediaResource raises KeyError instead of ValidationError when path fails validation
Bug Description
MediaResource.validate_mimetype reads info.data["path"] with a subscript:
# llama-index-core/llama_index/core/schema.py:589
rpath: str | None = info.data["path"]In pydantic, a field that failed validation is absent from info.data. So when path is
invalid, this subscript raises KeyError before pydantic can assemble the ValidationError.
The sibling lookup in the same validator, six lines above, already guards itself:
b64_data = info.data.get("data") # line 583, guarded
...
rpath: str | None = info.data["path"] # line 589, not guardedSo the two lines are inconsistent with each other. This reads as an oversight rather than a decision.
| Expected | pydantic.ValidationError, naming the path field and its type error |
| Actual | KeyError: 'path', raised from inside the validator |
Why it matters, beyond the exception type:
- The caller cannot
except ValidationError, which is the documented way to handle a bad model. - The real reason for the failure never reaches them.
KeyError: 'path'reads like an internal bug in llama-index, not like "you passed the wrong type".
Version
llama-index-core 0.14.25. Reproduced on main at 169e450aa, Python 3.12 and 3.10, Linux.
Steps to Reproduce
from pathlib import PurePosixPath
from llama_index.core.schema import MediaResource
MediaResource(path=12345)
MediaResource(path=object())
MediaResource(path=PurePosixPath("a/b.txt"))| call | raised | expected |
|---|---|---|
MediaResource(path=12345) |
KeyError: 'path' |
ValidationError |
MediaResource(path=object()) |
KeyError: 'path' |
ValidationError |
MediaResource(path=PurePosixPath("a/b.txt")) |
KeyError: 'path' |
ValidationError |
The PurePosixPath case is the one most likely to be hit by accident. It looks like a path and
reads like one, but FilePath does not accept it, so the failure surfaces as a KeyError about a
dictionary the caller has never heard of.
Relevant Logs/Tracebacks
@classmethod
def validate_mimetype(cls, v: str | None, info: ValidationInfo) -> str | None:
if v is not None:
return v
# Since this field validator runs after the one for `data`
# then the contents of `data` should be encoded already
b64_data = info.data.get("data")
if b64_data: # encoded bytes
decoded_data = base64.b64decode(b64_data)
if guess := filetype.guess(decoded_data):
return guess.mime
# guess from path
> rpath: str | None = info.data["path"]
^^^^^^^^^^^^^^^^^
E KeyError: 'path'
llama_index/core/schema.py:589: KeyErrorThe fix
One word, making line 589 match line 583:
- rpath: str | None = info.data["path"]
+ rpath: str | None = info.data.get("path")rpath is already annotated str | None and the very next line is if rpath:, so a None from a
failed or absent field is already handled. Nothing downstream changes.
Tests
Added to llama-index-core/tests/schema/test_media_resource.py.
| test | pins | fails without the change |
|---|---|---|
test_invalid_path_raises_validation_error |
an invalid path surfaces as ValidationError for all three types above |
yes |
Watched in both directions, at 169e450aa, Python 3.12:
| result | |
|---|---|
| the test alone, before the fix | 1 failed, 6 passed, with KeyError: 'path' at schema.py:589 |
| the same test, after the fix | 7 passed |
Collateral
Same tree, llama-index-core, in a container mirroring unit_test.yml:
| surface | before | after |
|---|---|---|
| core, py3.12 | 1499 passed, 22 skipped, 6 xfailed | 1500 passed, 22 skipped, 6 xfailed |
| core, py3.10 | 1499 passed, 22 skipped, 6 xfailed | 1500 passed, 22 skipped, 6 xfailed |
| warnings, py3.12 | 2128 | 2126, none new |
| warnings, py3.10 | 2114 | 2114, unchanged |
llama-dev test --cov --cov-fail-under=50 |
passed | passed |
pre-commit on the changed files |
n/a | all hooks pass |
| mypy | passed | passed |
There are no pre-existing failures on either interpreter, so the plus one is the new test and nothing else moved.
Scope
In scope: the single unguarded lookup at line 589, and a test for it.
Out of scope, deliberately:
- The
urlserialization and validation in the same class. Not affected, sincepathis the only field read out ofinfo.databy subscript. MediaResource.hashembedding the OS path separator. That is #23058, which already has two open PRs, and I am not sending a third.MediaResource.serialize_path(line 603) andImageDocument.image_path(line 1372), which carry the same separator fault as #23058 and are not covered by either of its PRs. Reported in a comment on #23058 rather than here, because they are a different mechanism from this one.- Any change to what
FilePathaccepts. MakingPurePosixPathvalid would be a feature decision, not a bug fix, and it is not what this issue asks for.
Definition of done
MediaResource(path=<invalid>)raisesValidationError.- A named test covers it and fails without the change.
- The core suite is unchanged apart from that test, on 3.12 and on 3.10.
- No new warnings.
I have the change and the test ready and green on all of the above. Happy to open the PR, or to leave it if you would rather fold it into something larger.
AI-assisted, disclosed per CONTRIBUTING.md. Every measurement above is output from a command that was run.
Source: run-llama/llama_index