Repository navigation
Conversation
Zulip's uploaded-file endpoints (/user_uploads/...) live outside the /api/v1/ namespace, so client.call_endpoint() cannot be used to reach them -- it unconditionally prepends /api/v1/ to every path. This commit adds a dedicated Client.fetch_upload(path) method that: * Accepts a relative path (e.g. /user_uploads/2/ab/cat.png) extracted directly from a Zulip message's markdown link, or a fully-qualified URL on the same Zulip server. * Sends the request through the existing authenticated session (HTTP Basic Auth with the bot's email and API key), so no separate credential management is required. * Returns a raw requests.Response object -- not JSON -- because the response is binary file content, not a Zulip API envelope. A corresponding example script (examples/download-file) and a full test suite (zulip/tests/test_fetch_upload.py, 11 tests) are included. Fixes zulip#628.
0fc8388 to
a4e87a7
Compare
|
Took a look through the implementation and the static-analysis CI failure. A couple of quick observations:
The tests in |
Fixes #628
Problem
client.call_endpoint()unconditionally prepends/api/v1/to everyURL, making it impossible to request endpoints that live outside that
namespace. The most important case is Zulip's uploaded-file endpoints:
GET /user_uploads/<realm>/<filename>— downloads the raw fileGET /user_uploads/temporary/<token>/<filename>— temporary S3 linkA bot that tries to download an attachment via
call_endpoint()silentlyhits
/api/v1/user_uploads/...instead (which returns a JSONtemporary-URL response), and then crashes when
do_api_query()tries todecode binary file bytes as JSON.
Solution
Add a dedicated
Client.fetch_upload(path)method that:Does NOT prepend
/api/v1/— it builds the target URL from theserver root (stripping the
/api/suffix fromself.base_url).Reuses the authenticated session (
self.session, which alreadycarries HTTP Basic Auth with the bot's email and API key), so no
separate credential plumbing is needed.
Accepts both forms of path:
/user_uploads/2/ab/cat.png) taken directly froma Zulip message's markdown link
Returns a raw
requests.Response(notDict[str, Any]), becausethe response body is binary file content, not a JSON API envelope.
Example usage (from a bot)