Skip to content

v1.13.0

Latest

Choose a tag to compare

@github-actions github-actions released this 27 May 14:37
a70e46f

Release theme: full group-DM coverage. Three PRs landed back-to-back wrapping the entire /api/v1/messages/groups/* and /api/v1/messages/* surface (lifecycle + members; state + search; per-message ops + attachments + group avatar). 38 new SDK methods total across sync + async + mock, plus new multipart-upload + binary-download transport helpers.

New methods

  • DM per-message ops + attachments + group avatar — completes group-DM coverage. Third and final PR of the group-DM coverage series. 15 new methods (sync + async + mock) plus brand-new multipart-upload + binary-download infrastructure. With this in, the SDK now wraps the full /api/v1/messages/* surface; a follow-up release PR will bump the version.

    Per-message operations (the same surface for 1:1 and group):

    • mark_message_read(message_id) / list_message_reads(message_id)
    • add_message_reaction(message_id, emoji) / remove_message_reaction(message_id, emoji) — emoji is URL-encoded in the DELETE path so multi-byte codepoints don't corrupt the URL
    • edit_message(message_id, body) — 5-minute edit window enforced server-side
    • list_message_edits(message_id) — walk the edit timeline
    • delete_message(message_id) — sender-only soft delete
    • toggle_star_message(message_id) — toggle the caller's bookmark
    • list_saved_messages(limit=50, offset=0) — paginated starred list
    • forward_message(message_id, recipient_username, comment="") — forward as a new 1:1 with quoted body

    Attachments (multipart):

    • upload_message_attachment(filename, file_bytes, content_type)
    • delete_message_attachment(attachment_id)
    • get_message_attachment(attachment_id, variant="full") → raw bytes (or "thumb")

    Group avatar (multipart):

    • upload_group_avatar(conv_id, filename, file_bytes, content_type)
    • get_group_avatar(conv_id) → raw bytes

    Infrastructure added in the same PR:

    • _raw_multipart_upload — RFC 7578 envelope hand-rolled on the sync client (urllib has no native multipart support); the async client uses httpx's native files= argument. Filename quotes and backslashes are escaped per RFC 6266 §4.2 so the multipart envelope stays parseable.
    • _raw_request_bytes — GET helper returning raw bytes, distinct from _raw_request's JSON path. Auth, hook callbacks, and rate-limit header tracking all behave identically; the retry loop is deliberately skipped (uploads + downloads are rarely safe to retry blindly).
    • Both helpers share the same _build_api_error plumbing so error envelopes look identical to JSON callers (ColonyAPIError, ColonyAuthError, ColonyNetworkError).

    MockColonyClient records byte-length (not raw bytes) for upload calls so test assertion shapes stay grep-able for large payloads. Bytes-returning getters yield a deterministic sentinel by default, overridable via responses={"get_message_attachment": b"..."}. 67 new tests cover the happy paths, the RFC 6266 filename-escape, the 413 / 403 error envelopes, network-error wrapping, lazy-token minting, and the request/response hook fan-out. 100% line coverage preserved.

  • Group DM conversations — state + search. 10 new methods (sync + async + mock) layer over the lifecycle methods landed in the prior PR. Second of three PRs; group avatar uploads were pulled out of this PR and will land with the attachments work in PR 3 (they share a multipart-upload transport that the SDK doesn't yet have).

    State (all per-participant — muting / snoozing affects only the caller's notifications, not the room):

    • mute_group_conversation(conv_id, until=None) → omit until (or pass "forever") for a permanent mute; other tokens: "1h", "8h", "1d", "1w"
    • unmute_group_conversation(conv_id) — idempotent
    • snooze_group_conversation(conv_id, duration) → required token: "1h", "3h", "until_morning", "1d", "1w". No "snooze forever" — use mute instead
    • unsnooze_group_conversation(conv_id) — idempotent
    • set_group_read_receipts(conv_id, show=None) → three-state override: True forces on, False forces off, None (default) clears the override and falls back to the user-level preference

    Pins (group-wide, admin-only):

    • pin_group_message(conv_id, msg_id)
    • unpin_group_message(conv_id, msg_id) — idempotent

    Search:

    • search_group_messages(conv_id, q, limit=50, offset=0) → PostgreSQL FTS within a single group. Returns {hits, total, has_more} with <mark>…</mark> highlights pre-rendered.

    MockColonyClient records each call into client.calls. 35 new tests cover the three-state set-receipts surface (true/false/None), the lowercase-bool quirk on FastAPI query coercion, query-string escaping, and pagination defaults.

  • Group DM conversations — lifecycle + members. 13 new methods (sync + async + mock) wrap the group-DM surface that landed on the backend over the last six weeks (/api/v1/messages/groups/*). This is the first of three PRs that complete group-DM coverage in the SDK; per-message ops + attachments follow. No version bump yet — the version moves with the final PR once the surface is complete.

    Lifecycle:

    • create_group_conversation(title, members) → invite 1..49 usernames; caller is auto-added as the creator/admin
    • list_group_templates() → pre-configured group shapes (software team, research pod, etc.) with slug to feed into the next call
    • create_group_from_template(template, members, title_override=None) → seed a group from a template
    • get_group_conversation(conv_id, limit=50, offset=0) → fetch the group + its recent messages
    • update_group_conversation(conv_id, title=None, description=None) → rename + set description (omit fields you don't want to touch; pass "" to clear description explicitly)
    • send_group_message(conv_id, body, reply_to_message_id=None, idempotency_key=None) → post to a group, optionally replying to a quoted parent. Note: idempotency_key is only threaded through on the sync client — the async transport doesn't yet pass the Idempotency-Key header (same gap as the existing 1:1 send_message).

    Member management:

    • list_group_members(conv_id)
    • add_group_member(conv_id, username) → admin-only; invitee starts in pending invite status until they accept
    • remove_group_member(conv_id, user_id) → admin-only
    • set_group_admin(conv_id, user_id, is_admin) → promote/demote
    • transfer_group_creator(conv_id, new_creator_username) → hand the creator role to another member
    • respond_to_group_invite(conv_id, accept) → invitee-side accept/decline
    • mark_group_all_read(conv_id) → bulk-mark every message in a group as read

    Query-param-shaped endpoints (the server's choice for v1 simplicity) are URL-encoded by the SDK; booleans use the lowercase "true"/"false" FastAPI expects, not Python's default capitalised str(bool). MockColonyClient records each call into client.calls exactly like the existing methods. 53 new regression tests cover request shape, header threading, default-vs-omitted parameters, and the mock recording surface.

Internal

  • Hoisted inline urllib.parse imports to module top. Both clients had accumulated 29 inline from urllib.parse import urlencode (plus one quote) reimports scattered through individual methods as the group-DM surface grew. None were conditional or lazy — they all fired on first call regardless. Consolidated to a single top-level import in each file (from urllib.parse import quote, urlencode). No behaviour change; net -55 lines.

Tests

  • Group-DM integration tests. New tests/integration/test_group_messages.py exercises the live round trip across two real test accounts: create → list members → send (both directions) → mark-all-read. Documents three places where the live server's response shape differs from the in-method docstrings (get_group_conversation returns a slim envelope, invites auto-accept between trusted accounts, mark_group_all_read returns {marked: int} not {marked_read: int}). Module-scoped fixture keeps the create-group call count down for the 12/hour rate-limit budget.