6 Commits
Author SHA1 Message Date
dsql 675eac310e refactor: drop the pass-through try/except around channel.send
T3-21: except discord.HTTPException: raise did nothing; the exception already
propagates unwrapped.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-10-16 04:20:22 -04:00
dsql caf0c3ee5c fix: cap cache_url at 100 MiB, the largest upload a bot can make
bots can't have nitro, so discord's largest upload a bot can make is a boost
tier 3 guild's 104857600 bytes (discord.py Guild.filesize_limit). 500 MiB
admitted bodies no bot upload could carry.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-10-16 04:20:08 -04:00
dsql 40d38661d1 chore: bump to 1.2.0
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-10-16 04:13:54 -04:00
dsql 8e167368a4 fix: cache_url caps the remote body at MAX_CACHE_URL_BYTES
T2-15: cache_url buffered the whole remote body with response.read(), no cap
(a 200 MB body sat in memory before discord rejected it). refuse a
Content-Length over the cap, and stream with iter_chunked, raising
DPYCacheError once the running total passes it. the cap (exported module
constant) is 500 MiB, discord's largest upload tier, so nothing discord
would accept is refused.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-10-16 04:13:54 -04:00
dsql 371428bb6b chore: bump to 1.1.0 (logging-discipline audit)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-08-10 23:00:50 -04:00
dsql b2bbca0cec fix: drop log-and-raise on the storage-channel send failure
the HTTPException handler logged at ERROR then re-raised - raise XOR log. HTTPException
propagates unwrapped per the documented error contract, so it already carries the failure
to the caller; the error log double-reported it. drop the log, keep the raise. the
genuine swallow-path WARNING elsewhere in the file is unchanged.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-08-09 02:12:29 -04:00
4 changed files with 25 additions and 13 deletions

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ bites you.
## Install
```
dpy_cache @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_cache.git@v1.0.0
dpy_cache @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_cache.git@v1.2.0
```
## Usage
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "dpy_cache"
version = "1.0.0"
version = "1.2.0"
description = "Persist images/files to a Discord storage channel and get durable, re-resolvable CDN references — config-free, injectable, installable."
requires-python = ">=3.10"
dependencies = [
+2
View File
@@ -6,6 +6,7 @@ from importlib.metadata import PackageNotFoundError, version
from .dpy_cache import (
MAX_ATTACHMENTS_PER_MESSAGE,
MAX_CACHE_URL_BYTES,
CacheResult,
DPYCache,
DPYCacheError,
@@ -25,5 +26,6 @@ __all__ = [
"CacheResult",
"FileContent",
"MAX_ATTACHMENTS_PER_MESSAGE",
"MAX_CACHE_URL_BYTES",
"__version__",
]
+21 -11
View File
@@ -41,10 +41,11 @@ error contract (fail-loud)
--------------------------
nothing is swallowed. lib-specific invariants raise :class:`DPYCacheError` (attachment-count
mismatch after a send, a duplicate filename in a list input to ``cache()``, ``cache_url``
non-200, a ``lookup`` jump-url pointing at a different channel than the injected one, a
``discord.File`` source that can't be safely rebuilt for a repeat send). unnamed ``bytes`` in
a list input raises ``ValueError``. raw discord errors (``Forbidden`` / ``HTTPException`` /
``NotFound``) propagate UNWRAPPED so callers can still branch on discord's types.
non-200 or a body over ``MAX_CACHE_URL_BYTES``, a ``lookup`` jump-url pointing at a
different channel than the injected one, a ``discord.File`` source that can't be safely
rebuilt for a repeat send). unnamed ``bytes`` in a list input raises ``ValueError``. raw
discord errors (``Forbidden`` / ``HTTPException`` / ``NotFound``) propagate UNWRAPPED so
callers can still branch on discord's types.
"""
from __future__ import annotations
@@ -60,6 +61,10 @@ import discord
log = logging.getLogger(__name__)
MAX_ATTACHMENTS_PER_MESSAGE = 10
# cache_url's body cap: the largest upload a BOT can make (boost tier 3 guild,
# discord.py Guild.filesize_limit 104857600; bots can't have nitro), so nothing discord would
# accept from a bot is refused; it bounds memory, not the channel's own (tier) limit
MAX_CACHE_URL_BYTES = 100 * 1024 * 1024
FileContent = Union[bytes, bytearray, str, "os.PathLike[str]", discord.File]
@@ -135,11 +140,7 @@ class DPYCache:
for start in range(0, len(items), MAX_ATTACHMENTS_PER_MESSAGE):
group = items[start:start + MAX_ATTACHMENTS_PER_MESSAGE]
discord_files = [self._to_file(name, source) for name, source in group]
try:
message = await self._channel.send(content=content, files=discord_files)
except discord.HTTPException:
log.error("dpy_cache: send of %d file(s) to the storage channel failed", len(group))
raise
message = await self._channel.send(content=content, files=discord_files)
attachments = list(message.attachments)
if len(attachments) != len(group):
@@ -177,12 +178,21 @@ class DPYCache:
content: "str | None" = None,
) -> FileRef:
"""fetch a remote url then cache its bytes as ``name``; raises DPYCacheError on
non-200"""
non-200, or when the body (declared or streamed) exceeds ``MAX_CACHE_URL_BYTES``"""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status != 200:
raise DPYCacheError(f"cache_url: GET {url} returned status {response.status}")
data = await response.read()
if (response.content_length or 0) > MAX_CACHE_URL_BYTES:
raise DPYCacheError(
f"cache_url: GET {url} is {response.content_length} bytes, over the "
f"{MAX_CACHE_URL_BYTES}-byte cap"
)
data = bytearray()
async for chunk in response.content.iter_chunked(1 << 16):
data += chunk
if len(data) > MAX_CACHE_URL_BYTES:
raise DPYCacheError(f"cache_url: GET {url} body exceeds the {MAX_CACHE_URL_BYTES}-byte cap")
return await self.cache_one(name, data, content=content)
async def resolve(self, ref: FileRef) -> str: