From 51a4f7a8170ece1adf91ccbd52380b26c3a351cb Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 6 Jul 2026 20:02:19 -0400 Subject: [PATCH] fix: pre-validate all cache() inputs before any upload so a bad late input orphans nothing cache() built discord.File objects per batch inside the send loop, so an unsupported type or a missing file path in the 11th+ item raised only after earlier batches were already uploaded - orphaning those stored messages with no returned ref. A new _prevalidate pass checks every source up front (supported type; a path source exists and is a file) WITHOUT opening any File or reading bytes, so a locally-detectable bad input fails loud before the first send. The File is still built per batch in the loop (fresh-File-per-send unchanged), so no extra open FDs are held. Neg control: old code uploaded batch 1 then raised on the bad path (sends=1, orphaned); new raises with sends=0. Signed-off-by: disqualifier --- src/dpy_cache/dpy_cache.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/dpy_cache/dpy_cache.py b/src/dpy_cache/dpy_cache.py index 928cd9f..9b3ba99 100644 --- a/src/dpy_cache/dpy_cache.py +++ b/src/dpy_cache/dpy_cache.py @@ -122,10 +122,13 @@ class DPYCache: ) -> CacheResult: """upload files to the storage channel (batching over 10/message) and return refs keyed by original filename; raises ValueError on unnamed bytes in a list input, - DPYCacheError on a duplicate filename in a list input (checked before any upload, so - a collision costs zero requests) or an attachment-count mismatch after a send, and - propagates discord's own Forbidden/HTTPException unwrapped on a send failure""" + DPYCacheError on a duplicate filename in a list input, an unsupported content type, + or a missing file path (all checked before any upload, so a locally-detectable bad + input in a >10-file batch costs zero requests and orphans nothing), or an + attachment-count mismatch after a send, and propagates discord's own + Forbidden/HTTPException unwrapped on a send failure""" items = self._normalize(files) + self._prevalidate(items) merged: dict[str, FileRef] = {} jump_urls: list[str] = [] @@ -248,6 +251,27 @@ class DPYCache: items.append((name, source)) return items + def _prevalidate(self, items: "list[tuple[str, FileContent]]") -> None: + """check every source is a supported, locally-resolvable input BEFORE any upload + + catches an unsupported content type or a missing/directory file path up front so a + bad input in a >10-file batch fails loud before the first send rather than after + earlier batches already landed (which would orphan those stored messages). does NOT + open Files or read bytes - only a cheap type check and, for a path source, an + existence/is-file probe; the actual File is still built per batch in the send loop. + """ + for name, source in items: + if isinstance(source, (discord.File, bytes, bytearray)): + continue + if isinstance(source, (str, os.PathLike)): + path = os.fspath(source) + if not os.path.isfile(path): + raise DPYCacheError( + f"file source for {name!r} is not an existing file: {path!r}" + ) + continue + raise ValueError(f"unsupported file content type for {name!r}: {type(source).__name__}") + def _to_file(self, name: str, source: FileContent) -> discord.File: """build a genuinely fresh discord.File for ``source`` named ``name``, distinct from any File previously handed to a send; a fresh File is built per send so the source