Skip to content

[CHA-1578] refactor: code generated from openapi specs#205

Open
mogita wants to merge 6 commits intomainfrom
cha-1578_openapi-refactor-codegen
Open

[CHA-1578] refactor: code generated from openapi specs#205
mogita wants to merge 6 commits intomainfrom
cha-1578_openapi-refactor-codegen

Conversation

@mogita
Copy link

@mogita mogita commented Dec 16, 2025

Summary by CodeRabbit

  • New Features

    • Added comprehensive webhook handling with event parsing, type extraction, and HMAC-SHA256 signature verification.
    • Added moderation appeals API endpoints for creating, retrieving, and querying appeals.
    • Added video recording control endpoints (start/stop recording with external storage support).
    • Added call participant session metrics and query endpoints for detailed analytics.
    • Added Import V2 tasks API for bulk data operations.
    • Added future channel bans query endpoint.
  • Bug Fixes

    • Updated response types for message action methods to properly reflect action-oriented responses.
    • Improved datetime serialization in query parameters.

Copilot AI review requested due to automatic review settings December 16, 2025 05:58
@coderabbitai
Copy link

coderabbitai bot commented Dec 16, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors request payload construction across multiple modules using dictionary expansion patterns, introduces new API endpoints for recording, appeals, and data imports, launches a comprehensive webhook event handling system with constants and parsing utilities, and updates method return types and parameter types throughout chat, video, moderation, and feeds modules.

Changes

Cohort / File(s) Summary
Chat REST API Refactoring
getstream/chat/async_rest_client.py, getstream/chat/rest_client.py
Rewrote request payload construction using **{...} dictionary expansion; added query_future_channel_bans endpoint; changed return types for run_message_action, commit_message, translate_message to MessageActionResponse; added push_level parameter to channel type methods; updated numerous method signatures for improved parameter handling.
Video REST API Enhancement
getstream/video/async_rest_client.py, getstream/video/rest_client.py
Added new recording endpoints (start_recording, stop_recording); added call metrics endpoints (get_call_participant_session_metrics, query_call_participant_sessions); renamed SIP routing paths from /routing_rules to /inbound_routing_rules; updated notification settings type hints to Request variants; refactored query parameter construction to use dictionary expansion.
Video Call Wrapper Updates
getstream/video/async_call.py, getstream/video/call.py
Added four new public methods (start_recording, stop_recording, get_call_participant_session_metrics, query_call_participant_sessions); removed obsolete parameterless start_recording and stop_recording variants; delegated calls to updated client methods with proper response syncing.
Moderation API Expansion
getstream/moderation/async_rest_client.py, getstream/moderation/rest_client.py
Added appeal-related endpoints (appeal, get_appeal, query_appeals); updated payload type hints to use Request/Payload variants; refactored body construction with dictionary expansion; expanded submit_action with new action fields (appeal_id, reject_appeal, restore, unblock); updated sort parameter types.
Feeds API & Client Refactoring
getstream/feeds/rest_client.py, getstream/feeds/feeds.py
Added batch and restoration methods (update_activities_partial_batch, restore_activity); expanded activity, bookmark, follow, and feed operations with new optional parameters (copy_custom_to_notification, delete_notification_activity, skip_push, etc.); refactored all JSON/query body construction using dictionary expansion; added friend_reactions_options parameter to feed creation.
Common Client Updates
getstream/common/async_rest_client.py, getstream/common/rest_client.py
Added import v2 task endpoints (list_import_v2_tasks, create_import_v2_task, delete_import_v2_task, get_import_v2_task); updated upsert_push_provider parameter type to PushProviderRequest; refactored request body construction across multiple endpoints using dictionary expansion.
Webhook Module (New)
getstream/webhook.py, getstream/tests/test_webhook.py
Introduced comprehensive webhook handling: added 140+ EVENT_TYPE_* constants covering appeal, call, channel, feeds, message, moderation, reaction, and user events; implemented get_event_type(), parse_webhook_event(), and verify_webhook_signature() functions for event extraction, deserialization, and HMAC-SHA256 validation; added extensive test coverage for all webhook functionality.
Utilities & Misc
getstream/utils/__init__.py, getstream/video/rtc/coordinator/ws.py, tests/test_video_examples.py
Added datetime serialization support in build_query_param() with ISO 8601 formatting; updated WebSocket type annotation from WebSocketClientProtocol to ClientConnection with added null-safety assertion; updated video recording test to use explicit recording_type="composite" parameter.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


🐰 Twitching whiskers with glee,
Dictionary unpacking, wild and free!
Webhooks dance, recordings shine,
Appeals and imports—code divine!
Stream's refactored, better designed,
A rabbit's work, exquisitely signed! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR addresses the core objective of CHA-1578 by regenerating code from OpenAPI specs across multiple files, though Kotlin support is not evident in the changes. Verify whether Kotlin support was added as a secondary objective, or confirm it's deferred to a separate PR or task.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly indicates this is a refactoring PR focused on code generation from OpenAPI specs, which aligns with the main objective of the changeset.
Out of Scope Changes check ✅ Passed The changes appear focused on code generation refactoring with payload construction updates and new API endpoints, which aligns with OpenAPI spec regeneration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cha-1578_openapi-refactor-codegen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR contains code generated from refactored OpenAPI specifications, updating multiple REST client interfaces and model definitions across the GetStream SDK. The changes primarily involve type renames, new API endpoints, parameter additions, and model restructuring to align with updated API specifications.

Key changes:

  • Added new API methods and endpoints (e.g., get_call_stats_map, collections CRUD operations, mark_delivered)
  • Renamed types for clarity (e.g., NotificationSettingsNotificationSettingsRequest/Response, action request types now use Payload suffix)
  • Added telemetry decorators to feeds client methods
  • Expanded model definitions with new fields and response types

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
getstream/video/rest_client.py Added get_call_stats_map method, added pagination params to query_call_session_participant_stats, updated type names for notification settings and SIP challenge
getstream/video/async_rest_client.py Mirror changes to sync client for async version
getstream/moderation/rest_client.py Updated type names for moderation payloads and action requests (added Payload suffix)
getstream/moderation/async_rest_client.py Mirror changes to sync client for async version
getstream/models/__init__.py Extensive model changes: renamed/restructured many dataclasses, added new models for collections, events, notifications; removed deprecated models
getstream/feeds/rest_client.py Added telemetry decorators, new collection endpoints, renamed add_reactionadd_activity_reaction, added new parameters to existing methods
getstream/feeds/feeds.py Updated method signatures to match client changes
getstream/chat/rest_client.py Added mark_delivered method, updated type names, added message_timestamp parameter
getstream/chat/async_rest_client.py Mirror changes to sync client for async version
getstream/chat/channel.py Updated method signatures to match client changes
getstream/chat/async_channel.py Mirror changes to sync client for async version

The changes appear to be auto-generated code with consistent patterns applied across sync and async versions of the clients. No issues were identified in the generated code structure.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 30dc844 and f9779b3.

📒 Files selected for processing (10)
  • getstream/chat/async_channel.py (3 hunks)
  • getstream/chat/async_rest_client.py (4 hunks)
  • getstream/chat/channel.py (3 hunks)
  • getstream/chat/rest_client.py (4 hunks)
  • getstream/feeds/feeds.py (2 hunks)
  • getstream/feeds/rest_client.py (51 hunks)
  • getstream/moderation/async_rest_client.py (4 hunks)
  • getstream/moderation/rest_client.py (4 hunks)
  • getstream/video/async_rest_client.py (5 hunks)
  • getstream/video/rest_client.py (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Plugins that work with audio, video, or WebRTC functionality should depend on getstream[webrtc] instead of just getstream to access WebRTC-related dependencies like aiortc, numpy, torch, torchaudio, soundfile, scipy, deepgram-sdk, and elevenlabs

Files:

  • getstream/chat/async_channel.py
  • getstream/chat/async_rest_client.py
  • getstream/moderation/rest_client.py
  • getstream/video/async_rest_client.py
  • getstream/chat/channel.py
  • getstream/moderation/async_rest_client.py
  • getstream/video/rest_client.py
  • getstream/chat/rest_client.py
  • getstream/feeds/rest_client.py
  • getstream/feeds/feeds.py
🧬 Code graph analysis (8)
getstream/chat/async_channel.py (1)
getstream/models/__init__.py (1)
  • ChannelInputRequest (4823-4851)
getstream/chat/async_rest_client.py (5)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/chat/rest_client.py (1)
  • mark_delivered (145-157)
getstream/models/__init__.py (3)
  • DeliveredMessagePayload (7418-7420)
  • MarkDeliveredResponse (10927-10928)
  • ChannelInputRequest (4823-4851)
getstream/utils/__init__.py (2)
  • build_query_param (94-126)
  • build_body_dict (151-175)
getstream/base.py (2)
  • post (240-256)
  • post (429-445)
getstream/video/async_rest_client.py (3)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/models/__init__.py (4)
  • QueryCallStatsMapResponse (14474-14538)
  • QueryCallSessionParticipantStatsResponse (14424-14457)
  • NotificationSettingsRequest (12890-12908)
  • SIPChallengeRequest (16585-16627)
getstream/utils/__init__.py (1)
  • build_query_param (94-126)
getstream/chat/channel.py (2)
getstream/stream_response.py (1)
  • data (22-24)
getstream/models/__init__.py (1)
  • ChannelInputRequest (4823-4851)
getstream/moderation/async_rest_client.py (4)
getstream/models/__init__.py (11)
  • ModerationPayloadRequest (12502-12514)
  • BanActionRequestPayload (1901-1919)
  • BlockActionRequestPayload (2040-2043)
  • DeleteActivityRequestPayload (7131-7137)
  • DeleteCommentRequestPayload (7226-7232)
  • DeleteMessageRequestPayload (7291-7297)
  • DeleteReactionRequestPayload (7322-7328)
  • DeleteUserRequestPayload (7369-7384)
  • MarkReviewedRequestPayload (10956-10963)
  • ShadowBlockActionRequestPayload (17303-17306)
  • UnbanActionRequestPayload (18368-18371)
getstream/moderation/rest_client.py (2)
  • ban (35-58)
  • unban (489-508)
getstream/chat/async_rest_client.py (1)
  • delete_message (969-987)
getstream/chat/rest_client.py (1)
  • delete_message (959-977)
getstream/video/rest_client.py (4)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/video/async_rest_client.py (1)
  • get_call_stats_map (875-903)
getstream/models/__init__.py (4)
  • QueryCallStatsMapResponse (14474-14538)
  • QueryCallSessionParticipantStatsResponse (14424-14457)
  • NotificationSettingsRequest (12890-12908)
  • SIPChallengeRequest (16585-16627)
getstream/utils/__init__.py (1)
  • build_query_param (94-126)
getstream/feeds/rest_client.py (5)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/base.py (6)
  • post (240-256)
  • post (429-445)
  • delete (276-292)
  • delete (465-481)
  • get (222-238)
  • get (411-427)
getstream/utils/__init__.py (2)
  • build_body_dict (151-175)
  • build_query_param (94-126)
getstream/feeds/feeds.py (1)
  • delete (17-24)
getstream/stream.py (2)
  • feeds (211-212)
  • feeds (316-328)
getstream/feeds/feeds.py (1)
getstream/models/__init__.py (2)
  • FeedInput (8341-8357)
  • EnrichmentOptions (7693-7744)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Upload results
  • GitHub Check: Tests (3.11)
  • GitHub Check: Tests (3.12)
  • GitHub Check: Tests (3.13)
  • GitHub Check: Tests (3.10)
🔇 Additional comments (35)
getstream/video/async_rest_client.py (3)

874-903: LGTM: New get_call_stats_map endpoint added.

The new method follows the established patterns in the codebase with proper telemetry decoration, path parameter handling, and query parameter construction for optional filters.


1028-1028: Breaking change: notification_settings type updated.

The parameter type changed from NotificationSettings to NotificationSettingsRequest. This is a breaking change for callers passing the old type. Ensure migration documentation is provided.


1096-1096: Breaking change: challenge parameter type updated.

The parameter type changed from SIPChallenge to SIPChallengeRequest. Callers using the old type will need to update their code.

getstream/moderation/async_rest_client.py (4)

213-213: Breaking change: moderation_payload type updated to ModerationPayloadRequest.

Callers using the previous ModerationPayload type will need to migrate.


249-249: Breaking change: config parameter type updated.

Type changed from FeedsModerationTemplateConfig to FeedsModerationTemplateConfigPayload.


288-288: Type alignment: sort uses SortParamRequest.

This aligns with other methods in the codebase that already use SortParamRequest for sort parameters.


460-470: Breaking changes: All action payload types updated to *Payload variants.

Multiple parameter types changed:

  • BanActionRequestBanActionRequestPayload
  • BlockActionRequestBlockActionRequestPayload
  • CustomActionRequestCustomActionRequestPayload
  • DeleteActivityRequestDeleteActivityRequestPayload
  • DeleteCommentRequestDeleteCommentRequestPayload
  • DeleteMessageRequestDeleteMessageRequestPayload
  • DeleteReactionRequestDeleteReactionRequestPayload
  • DeleteUserRequestDeleteUserRequestPayload
  • MarkReviewedRequestMarkReviewedRequestPayload
  • ShadowBlockActionRequestShadowBlockActionRequestPayload
  • UnbanActionRequestUnbanActionRequestPayload

Callers will need to update their code to use the new payload types.

getstream/chat/channel.py (2)

71-71: Breaking change: data parameter type updated to ChannelInputRequest.

The parameter type changed from ChannelInput to ChannelInputRequest. Existing callers passing ChannelInput will need to migrate.


329-348: New optional message_timestamp parameter added to mark_unread.

This is an additive, non-breaking change that extends the API to support marking unread by timestamp in addition to message ID.

getstream/chat/async_channel.py (2)

71-71: Breaking change consistent with sync version.

The data parameter type changed to ChannelInputRequest, matching the sync Channel class.


329-348: Async version mirrors sync changes for mark_unread.

The message_timestamp parameter addition is consistent with the sync Channel.mark_unread method.

getstream/feeds/feeds.py (1)

26-66: New optional parameters added to get_or_create.

Two new optional parameters added:

  • id_around: Optional[str] - for around-based pagination
  • enrichment_options: Optional[EnrichmentOptions] - for controlling enrichment behavior

These are additive, non-breaking changes that extend the API surface.

getstream/chat/rest_client.py (3)

144-157: New mark_delivered endpoint added.

This new method allows marking messages as delivered for a user. The implementation follows the established patterns with proper telemetry decoration and parameter handling.


260-260: Breaking change: data parameter type updated in update_channel.

Type changed from ChannelInput to ChannelInputRequest, consistent with the Channel wrapper classes.


627-654: Extended mark_unread with message_timestamp parameter.

The new optional message_timestamp parameter is properly included in the request body via build_body_dict. This is a non-breaking additive change.

getstream/video/rest_client.py (4)

870-899: New get_call_stats_map endpoint looks correct.

The implementation correctly mirrors the async counterpart, using query parameters for optional filters and path parameters for required identifiers. The return type QueryCallStatsMapResponse aligns with the model definition.


1024-1024: Type update to NotificationSettingsRequest is consistent.

The change from NotificationSettings to NotificationSettingsRequest aligns with the new payload variant types for request bodies, matching the async client changes.


1062-1062: Type update in update_call_type matches create_call_type.

Consistent use of NotificationSettingsRequest across both create and update operations.


1090-1090: SIPChallengeRequest type update is appropriate.

The challenge parameter now uses the dedicated request payload type, which is consistent with the API schema refactoring pattern applied throughout this PR.

getstream/chat/async_rest_client.py (4)

146-159: New mark_delivered method implementation is correct.

The async implementation properly mirrors the sync counterpart, using user_id as a query parameter and latest_delivered_messages in the request body. Telemetry decorator is correctly applied.


264-264: Type update to ChannelInputRequest in update_channel is correct.

This aligns with the PR's schema updates for request payload types.


631-658: mark_unread extended with message_timestamp parameter.

The new optional datetime parameter is correctly added to the method signature and included in the request body via build_body_dict. This enhancement allows marking unread by timestamp in addition to message ID.


175-204: Async and sync clients are consistent; type difference between methods appears intentional.

Both the async and sync clients use ChannelInput for get_or_create_distinct_channel and ChannelInputRequest for update_channel. The type difference is not an inconsistency between sync/async implementations but rather reflects intentional schema differences: ChannelInput includes created_by_id, truncated_by_id, and filter_tags fields that ChannelInputRequest omits, and their config_overrides fields reference different types (ChannelConfig vs ConfigOverridesRequest), suggesting the endpoints accept different request structures.

getstream/moderation/rest_client.py (5)

203-225: custom_check correctly updated to use ModerationPayloadRequest.

The type update for moderation_payload parameter aligns with the new payload variant types.


243-252: v2_upsert_template updated with FeedsModerationTemplateConfigPayload.

The config parameter type change is consistent with the schema refactoring.


278-292: query_moderation_flags updated to use SortParamRequest.

Consistent with other methods using the new request type for sort parameters.


448-486: submit_action correctly updated with new payload variant types.

All action parameters (ban, block, custom, delete_*, mark_reviewed, shadow_block, unban) now use their respective ...RequestPayload types, aligning with the schema updates.


254-276: Consider clarifying the intent of type inconsistency in moderation methods.

The flag method (line 263) uses ModerationPayload while custom_check (line 211) uses ModerationPayloadRequest. This pattern is consistent across the async client as well, where check and flag use ModerationPayload, while only custom_check uses ModerationPayloadRequest. Since both types have identical structure, clarify whether this distinction is intentional (e.g., to explicitly mark custom_check as request-only) or if the methods should be unified to use the same type for consistency.

getstream/feeds/rest_client.py (7)

3-3: Telemetry import added for instrumentation.

The addition of telemetry enables observability across all Feeds API methods.


34-80: add_activity extended with new parameters and telemetry.

New optional parameters restrict_replies, skip_enrich_url, and collection_refs expand the API capabilities. Telemetry decorator correctly applied.


505-560: New collection CRUD endpoints added.

The delete_collections, read_collections, update_collections, create_collections, and upsert_collections methods provide comprehensive collection management. Each uses appropriate HTTP methods and parameter handling.


799-808: list_feed_groups extended with include_soft_deleted filter.

Correctly passed as a query parameter for the GET request.


1299-1329: New feed visibility endpoints added.

list_feed_visibilities, get_feed_visibility, and update_feed_visibility provide visibility management capabilities with consistent patterns.


1375-1395: New get_feeds_rate_limits endpoint.

Provides rate limit introspection with platform-specific filters (android, ios, web, server_side).


1604-1617: delete_feed_user_data changed from DELETE to POST.

The method now uses POST with a request body containing hard_delete. This is a semantic change from the typical DELETE pattern but is appropriate when the operation requires request body parameters.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
getstream/moderation/rest_client.py (1)

133-133: Use consistent payload type across check and custom_check methods.

The check method uses ModerationPayload (line 133), while custom_check uses ModerationPayloadRequest (line 258). Both classes are structurally identical, suggesting this naming inconsistency is likely unintentional. Standardize on a single type name for both methods.

getstream/video/async_call.py (1)

149-176: Avoid silent breaking changes from mid-signature parameter insertion (positional args).
go_live(...) and stop_live(...) add new optional parameters between existing ones, which can mis-bind existing positional calls without an exception. If positional args are supported for this public API, prefer appending new params at the end or make options keyword-only via * after self.

Also applies to: 427-453

getstream/video/rest_client.py (1)

951-980: Same sort: List[SortParamRequest] query-param encoding risk as async client.

getstream/video/call.py (1)

145-173: Public API: mid-signature param insertion can silently break positional callers.
Consider appending new options at the end or making them keyword-only (def go_live(self, *, ...)) to avoid wrong-parameter binding.

Also applies to: 407-433

getstream/feeds/feeds.py (1)

26-67: High-risk breaking change: id_around inserted before existing params may silently mis-bind positional calls.
If users call get_or_create(10) expecting limit=10, they’ll now pass id_around="10". Prefer keyword-only params or append new params at the end to preserve positional behavior.

getstream/video/async_rest_client.py (1)

955-984: Bug confirmed: sort: List[SortParamRequest] passed to build_query_param will be incorrectly encoded.
build_query_param converts list items with str(v) instead of calling to_json() on objects that have it. SortParamRequest has DataClassJsonMixin which provides to_json(), but the list branch at line 29 of getstream/utils/__init__.py bypasses it. This produces invalid wire format (e.g., "SortParamRequest(direction=1, ...)" instead of proper JSON).

The correct pattern is used elsewhere: pass sort to build_body_dict (as in getstream/common/rest_client.py:574-577). Other affected endpoints include getstream/feeds/rest_client.py and getstream/chat/rest_client.py.

🤖 Fix all issues with AI agents
In @getstream/feeds/rest_client.py:
- Around line 1592-1599: The current query_feeds_usage_stats method constructs
the request body with build_body_dict which yields {"_from": ...} but the API
expects the field mapped as "from"; fix by creating a
QueryFeedsUsageStatsRequest instance with _from and to, serialize that model for
the POST payload (replace the build_body_dict/json assignment with model
instantiation and model.dict()/serialize call per existing patterns), ensuring
the correct field name is sent; also apply the same change to the
query_aggregate_call_stats method in the video client.

In @getstream/video/async_rest_client.py:
- Around line 890-920: get_call_stats_map currently forwards start_time and
end_time datetime objects to build_query_param which json.dumps can't serialize;
convert start_time and end_time to ISO-8601 strings (e.g., via
start_time.isoformat()) before calling build_query_param, or add a small helper
to normalize datetimes to strings and use it in get_call_stats_map so
build_query_param only receives serializable values; keep the rest of the method
and path_params unchanged.

In @getstream/video/rest_client.py:
- Around line 749-766: The stop_recording method always sends an empty JSON body
because build_body_dict() returns {}; update stop_recording to avoid sending
'{}' when there are no fields by calling body = build_body_dict() and passing
json=body if body is truthy, otherwise pass json=None (or omit the json param)
to self.post; reference the stop_recording function and build_body_dict to
implement this conditional so the backend does not receive an unnecessary empty
object.
- Around line 886-916: The query builder is serializing datetime via json.dumps
and fails; update build_query_param to detect datetime (and datetime-like)
values and serialize them using the existing encode_datetime utility (or call
encode_datetime for the start_time/end_time before passing) so ISO strings are
produced; ensure build_query_param handles both naive and aware datetimes and
lists/dicts containing datetimes so calls from get_call_stats_map (and its async
counterpart) will no longer raise TypeError.

In @getstream/webhook.py:
- Around line 378-382: The code currently instantiates webhook event classes by
calling event_class(**data) which bypasses DataClassJsonMixin custom
deserialization (e.g., datetime_from_unix_ns); change the instantiation in the
function that calls _get_event_class to use the dataclass deserializer (call
event_class.from_dict(data) or the classmethod provided by DataClassJsonMixin)
so field mappings and datetime decoders are applied; ensure you import/handle
the from_dict method on the event classes and fall back to the current behavior
only if from_dict is not available.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f9779b3 and 03cad93.

📒 Files selected for processing (14)
  • getstream/chat/async_rest_client.py
  • getstream/chat/rest_client.py
  • getstream/common/async_rest_client.py
  • getstream/common/rest_client.py
  • getstream/feeds/feeds.py
  • getstream/feeds/rest_client.py
  • getstream/models/__init__.py
  • getstream/moderation/async_rest_client.py
  • getstream/moderation/rest_client.py
  • getstream/video/async_call.py
  • getstream/video/async_rest_client.py
  • getstream/video/call.py
  • getstream/video/rest_client.py
  • getstream/webhook.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Plugins that work with audio, video, or WebRTC functionality should depend on getstream[webrtc] instead of just getstream to access WebRTC-related dependencies like aiortc, numpy, torch, torchaudio, soundfile, scipy, deepgram-sdk, and elevenlabs

Files:

  • getstream/feeds/feeds.py
  • getstream/common/async_rest_client.py
  • getstream/common/rest_client.py
  • getstream/video/call.py
  • getstream/moderation/rest_client.py
  • getstream/chat/async_rest_client.py
  • getstream/video/async_call.py
  • getstream/chat/rest_client.py
  • getstream/moderation/async_rest_client.py
  • getstream/feeds/rest_client.py
  • getstream/video/rest_client.py
  • getstream/video/async_rest_client.py
  • getstream/webhook.py
🧬 Code graph analysis (12)
getstream/feeds/feeds.py (3)
getstream/stream_response.py (2)
  • data (22-24)
  • StreamResponse (11-36)
getstream/models/__init__.py (3)
  • FeedInput (8576-8592)
  • EnrichmentOptions (7921-7975)
  • UpdateFeedResponse (20025-20027)
getstream/feeds/rest_client.py (1)
  • update_feed (914-940)
getstream/common/async_rest_client.py (4)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/models/__init__.py (6)
  • ListImportV2TasksResponse (11189-11195)
  • ImportV2TaskSettings (10750-10756)
  • UserRequest (21095-21119)
  • CreateImportV2TaskResponse (6949-6973)
  • DeleteImportV2TaskResponse (7505-7506)
  • GetImportV2TaskResponse (10066-10090)
getstream/utils/__init__.py (2)
  • build_query_param (94-126)
  • build_body_dict (151-175)
getstream/base.py (6)
  • get (222-238)
  • get (411-427)
  • post (240-256)
  • post (429-445)
  • delete (276-292)
  • delete (465-481)
getstream/common/rest_client.py (4)
getstream/common/async_rest_client.py (4)
  • list_import_v2_tasks (432-438)
  • create_import_v2_task (441-453)
  • delete_import_v2_task (456-466)
  • get_import_v2_task (469-477)
getstream/models/__init__.py (6)
  • ListImportV2TasksResponse (11189-11195)
  • ImportV2TaskSettings (10750-10756)
  • UserRequest (21095-21119)
  • CreateImportV2TaskResponse (6949-6973)
  • DeleteImportV2TaskResponse (7505-7506)
  • GetImportV2TaskResponse (10066-10090)
getstream/utils/__init__.py (2)
  • build_query_param (94-126)
  • build_body_dict (151-175)
getstream/base.py (6)
  • get (222-238)
  • get (411-427)
  • post (240-256)
  • post (429-445)
  • delete (276-292)
  • delete (465-481)
getstream/video/call.py (4)
getstream/video/async_call.py (1)
  • stop_recording (455-463)
getstream/video/async_rest_client.py (1)
  • stop_recording (754-769)
getstream/video/rest_client.py (1)
  • stop_recording (750-765)
getstream/models/__init__.py (1)
  • StopRecordingResponse (18228-18229)
getstream/moderation/rest_client.py (1)
getstream/models/__init__.py (19)
  • UserRequest (21095-21119)
  • AppealResponse (1562-1564)
  • GetAppealResponse (9727-9731)
  • ModerationPayloadRequest (12953-12965)
  • BanActionRequestPayload (2044-2062)
  • BlockActionRequestPayload (2183-2186)
  • CustomActionRequestPayload (7100-7104)
  • DeleteActivityRequestPayload (7345-7351)
  • DeleteCommentRequestPayload (7440-7446)
  • DeleteMessageRequestPayload (7510-7516)
  • DeleteReactionRequestPayload (7541-7547)
  • DeleteUserRequestPayload (7588-7603)
  • MarkReviewedRequestPayload (11382-11392)
  • RejectAppealRequestPayload (16404-16405)
  • RestoreActionRequestPayload (16681-16684)
  • ShadowBlockActionRequestPayload (17837-17840)
  • UnbanActionRequestPayload (18934-18940)
  • UnblockActionRequestPayload (18959-18962)
  • SubmitActionResponse (18352-18359)
getstream/chat/async_rest_client.py (4)
getstream/chat/rest_client.py (1)
  • mark_delivered (145-157)
getstream/models/__init__.py (4)
  • DeliveredMessagePayload (7637-7639)
  • MarkDeliveredResponse (11315-11316)
  • ChannelInputRequest (4990-5018)
  • MessageActionResponse (11676-11680)
getstream/utils/__init__.py (2)
  • build_query_param (94-126)
  • build_body_dict (151-175)
getstream/base.py (2)
  • post (240-256)
  • post (429-445)
getstream/video/async_call.py (5)
getstream/video/async_rest_client.py (1)
  • stop_recording (754-769)
getstream/video/call.py (1)
  • stop_recording (435-443)
getstream/video/rest_client.py (1)
  • stop_recording (750-765)
getstream/stream_response.py (1)
  • StreamResponse (11-36)
getstream/models/__init__.py (1)
  • StopRecordingResponse (18228-18229)
getstream/chat/rest_client.py (5)
getstream/chat/async_rest_client.py (1)
  • mark_delivered (147-159)
getstream/models/__init__.py (4)
  • DeliveredMessagePayload (7637-7639)
  • MarkDeliveredResponse (11315-11316)
  • ChannelInputRequest (4990-5018)
  • MessageActionResponse (11676-11680)
getstream/stream_response.py (2)
  • StreamResponse (11-36)
  • data (22-24)
getstream/utils/__init__.py (2)
  • build_query_param (94-126)
  • build_body_dict (151-175)
getstream/base.py (2)
  • post (240-256)
  • post (429-445)
getstream/moderation/async_rest_client.py (3)
getstream/moderation/rest_client.py (5)
  • appeal (35-52)
  • get_appeal (55-61)
  • query_appeals (64-83)
  • ban (86-109)
  • unban (548-567)
getstream/models/__init__.py (19)
  • UserRequest (21095-21119)
  • AppealResponse (1562-1564)
  • GetAppealResponse (9727-9731)
  • QueryAppealsResponse (14781-14785)
  • BanActionRequestPayload (2044-2062)
  • BlockActionRequestPayload (2183-2186)
  • CustomActionRequestPayload (7100-7104)
  • DeleteActivityRequestPayload (7345-7351)
  • DeleteCommentRequestPayload (7440-7446)
  • DeleteMessageRequestPayload (7510-7516)
  • DeleteReactionRequestPayload (7541-7547)
  • DeleteUserRequestPayload (7588-7603)
  • MarkReviewedRequestPayload (11382-11392)
  • RejectAppealRequestPayload (16404-16405)
  • RestoreActionRequestPayload (16681-16684)
  • ShadowBlockActionRequestPayload (17837-17840)
  • UnbanActionRequestPayload (18934-18940)
  • UnblockActionRequestPayload (18959-18962)
  • SubmitActionResponse (18352-18359)
getstream/utils/__init__.py (1)
  • build_body_dict (151-175)
getstream/feeds/rest_client.py (5)
getstream/common/telemetry.py (1)
  • operation_name (429-441)
getstream/base.py (6)
  • post (240-256)
  • post (429-445)
  • delete (276-292)
  • delete (465-481)
  • get (222-238)
  • get (411-427)
getstream/stream_response.py (2)
  • StreamResponse (11-36)
  • data (22-24)
getstream/utils/__init__.py (2)
  • build_body_dict (151-175)
  • build_query_param (94-126)
getstream/feeds/feeds.py (1)
  • delete (17-24)
getstream/video/rest_client.py (2)
getstream/models/__init__.py (4)
  • QueryCallStatsMapResponse (14968-15032)
  • SortParamRequest (17992-17999)
  • QueryCallSessionParticipantStatsResponse (14918-14951)
  • NotificationSettingsRequest (13341-13359)
getstream/utils/__init__.py (2)
  • build_body_dict (151-175)
  • build_query_param (94-126)
getstream/webhook.py (1)
getstream/models/__init__.py (9)
  • CustomEvent (7151-7170)
  • AppealAcceptedEvent (1425-1449)
  • CallAcceptedEvent (2608-2620)
  • BlockedUserEvent (2287-2303)
  • ClosedCaptionEvent (5975-5990)
  • CallCreatedEvent (2701-2713)
  • ChannelCreatedEvent (4728-4770)
  • ChannelUpdatedEvent (5695-5743)
  • ChannelVisibleEvent (5747-5789)
🔇 Additional comments (40)
getstream/moderation/rest_client.py (5)

34-83: LGTM - New appeal endpoints implemented correctly.

The three new appeal-related methods (appeal, get_appeal, query_appeals) follow the established patterns in this file with proper telemetry decorators, body construction, and return types matching the corresponding response models.


314-314: Inconsistent payload type: flag uses ModerationPayload while custom_check uses ModerationPayloadRequest.

Similar to check, the flag method's moderation_payload parameter still uses ModerationPayload rather than ModerationPayloadRequest. The AI summary indicates this should have been changed to ModerationPayloadRequest.


262-262: Type updated to ModerationPayloadRequest.

This aligns with the refactored OpenAPI specs using *Request payload variants.


296-296: Type updated to FeedsModerationTemplateConfigPayload.

This matches the payload type refactoring pattern.


499-545: submit_action expanded with new parameters and updated payload types.

The method signature changes look correct:

  • Added appeal_id parameter to support appeal actions
  • item_id changed to Optional[str] to accommodate appeal-based actions
  • Payload types renamed to *RequestPayload variants
  • New action payloads added: reject_appeal, restore, unblock

The body construction correctly includes all new fields.

getstream/moderation/async_rest_client.py (6)

34-85: LGTM - Async appeal endpoints implemented correctly.

The async versions of appeal, get_appeal, and query_appeals correctly mirror the synchronous implementations with proper async/await usage.


135-135: Same inconsistency: check uses ModerationPayload instead of ModerationPayloadRequest.

This mirrors the same issue in the synchronous client. If custom_check was updated to use ModerationPayloadRequest, check should likely be updated as well for consistency.


320-320: Same inconsistency: flag uses ModerationPayload instead of ModerationPayloadRequest.

Mirrors the sync client issue. The AI summary indicates this should have been changed.


266-266: Type updated to ModerationPayloadRequest.

Consistent with the sync client update.


302-302: Type updated to FeedsModerationTemplateConfigPayload.

Consistent with the sync client update.


507-553: submit_action async version correctly mirrors sync implementation.

All parameter and payload type changes align with the synchronous version:

  • appeal_id added
  • item_id now Optional[str]
  • Payload types use *RequestPayload variants
  • New actions: reject_appeal, restore, unblock
getstream/webhook.py (5)

1-7: LGTM on imports structure.

Standard library imports and typing are appropriate for webhook handling functionality.


170-326: LGTM on event type constants.

Comprehensive coverage of webhook event types with clear naming conventions.


329-348: LGTM on get_event_type implementation.

The function handles all input types correctly with appropriate exception handling and documented failure behavior.


385-543: Event mapping is comprehensive and correct.

The distinction between "*" (CustomEvent) and "custom" (CustomVideoEvent) is intentional. The event_map dictionary is recreated on each call, which is a minor inefficiency but acceptable for generated code.


546-565: LGTM on signature verification.

The use of hmac.compare_digest() for timing-safe comparison is correct security practice, preventing timing attacks on signature validation.

getstream/video/async_call.py (1)

454-463: stop_recording() wrapper wiring looks correct.
Pure formatting/refactor; still forwards type/id and syncs response.

getstream/video/async_rest_client.py (4)

308-344: go_live / stop_live request-body propagation looks consistent.
New recording flags are included in the request body via build_body_dict(...) and forwarded correctly.

Also applies to: 718-752


1038-1057: Payload type shift to NotificationSettingsRequest looks correct.
Using NotificationSettingsRequest aligns with request-body semantics (vs response-ish models).

Also applies to: 1078-1102


1107-1123: resolve_sip_inbound(... challenge: SIPChallengeRequest ...) looks consistent with request typing.
The request body uses build_body_dict(challenge=challenge) and should serialize via the model’s to_dict.


753-770: Empty JSON body is consistent with established codebase patterns, but backend contract verification still required.

Both sync and async stop_recording implementations use identical patterns—calling build_body_dict() with no arguments. This matches the standardized approach elsewhere in the codebase for endpoints without request parameters. However, explicit confirmation of the backend API contract expectations for this endpoint should be validated against the API specification or integration tests.

getstream/video/rest_client.py (3)

304-340: go_live / stop_live propagation looks consistent with the async variant.

Also applies to: 714-748


1034-1051: NotificationSettingsRequest usage looks correct for create/update call type.

Also applies to: 1072-1096


1101-1117: SIPChallengeRequest typing looks consistent for request payload.

getstream/video/call.py (1)

434-444: stop_recording() wrapper looks correct (formatting-only change).

getstream/feeds/feeds.py (1)

68-87: update(...) new fields are forwarded correctly.
description, name, and filter_tags are passed through to client.update_feed(...) as expected.

getstream/common/async_rest_client.py (1)

431-478: Import V2 task endpoints look consistent and correctly shaped.
Verb/path usage and request construction follow the existing client patterns.

getstream/common/rest_client.py (1)

423-466: Sync Import V2 task endpoints look consistent with async variant.

getstream/chat/rest_client.py (3)

144-157: New mark_delivered endpoint looks good.

The implementation correctly passes user_id as a query parameter and latest_delivered_messages in the request body. This aligns with the async version in async_rest_client.py.


627-654: Addition of message_timestamp parameter looks correct.

The new optional message_timestamp parameter is properly added to both the method signature and the request body, consistent with the async version.


1042-1059: Return type change to MessageActionResponse is a breaking change.

The return type changed from MessageResponse to MessageActionResponse for run_message_action. This is a breaking change for existing consumers. The same applies to commit_message and translate_message below.

Since this is generated code, please ensure that downstream consumers are aware of these breaking changes to:

  • run_message_action (line 1049)
  • commit_message (line 1065)
  • translate_message (line 1190)
getstream/chat/async_rest_client.py (4)

146-159: Async mark_delivered implementation is consistent with sync version.

The implementation correctly mirrors the sync version with proper await usage.


264-264: Same ChannelInput vs ChannelInputRequest inconsistency as sync version.

As noted in rest_client.py, update_channel uses ChannelInputRequest while get_or_create_distinct_channel (line 182) and get_or_create_channel (line 529) still use ChannelInput.

Please confirm this is intentional per the OpenAPI spec.


631-658: mark_unread changes are consistent with sync version.

The message_timestamp parameter addition is correctly implemented.


1052-1085: Return type changes to MessageActionResponse mirror sync version.

The async versions of run_message_action, commit_message, and translate_message correctly use the updated MessageActionResponse return type.

getstream/feeds/rest_client.py (5)

3-3: Telemetry integration looks good.

The telemetry import and decorator usage across all methods follows the established pattern in the codebase.


509-564: New collection methods follow established patterns.

The delete_collections, read_collections, update_collections, create_collections, and upsert_collections methods are correctly implemented with appropriate HTTP methods and telemetry decorators.


1314-1344: New feed visibility methods look correct.

The list_feed_visibilities, get_feed_visibility, and update_feed_visibility methods follow the established REST patterns.


1346-1373: New batch operations follow established patterns.

The create_feeds_batch, delete_feeds_batch, and own_batch methods are correctly implemented.


1619-1632: delete_feed_user_data changed from DELETE to POST.

The method now uses self.post instead of self.delete and includes hard_delete in the request body rather than as a query parameter. This appears intentional based on the endpoint path /users/{user_id}/delete.

remove_filter_tags: Optional[List[str]] = None,
remove_members: Optional[List[str]] = None,
data: Optional[ChannelInput] = None,
data: Optional[ChannelInputRequest] = None,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all usages of ChannelInput and ChannelInputRequest in chat client files
echo "=== ChannelInput usages ==="
rg -n "ChannelInput[^R]" getstream/chat/rest_client.py getstream/chat/async_rest_client.py

echo ""
echo "=== ChannelInputRequest usages ==="
rg -n "ChannelInputRequest" getstream/chat/rest_client.py getstream/chat/async_rest_client.py

Repository: GetStream/stream-py

Length of output: 632


🏁 Script executed:

#!/bin/bash
# Get context around the lines where ChannelInput and ChannelInputRequest are used
echo "=== Context around line 178 (ChannelInput) ==="
sed -n '170,185p' getstream/chat/rest_client.py

echo ""
echo "=== Context around line 260 (ChannelInputRequest) ==="
sed -n '252,270p' getstream/chat/rest_client.py

echo ""
echo "=== Context around line 525 (ChannelInput) ==="
sed -n '517,535p' getstream/chat/rest_client.py

# Also check the imports and type definitions
echo ""
echo "=== Type definitions and imports ==="
rg -n "class ChannelInput|class ChannelInputRequest" getstream/

Repository: GetStream/stream-py

Length of output: 2575


🏁 Script executed:

#!/bin/bash
# Examine the type definitions to understand the difference
echo "=== ChannelInput definition ==="
sed -n '/^class ChannelInput/,/^class [A-Z]/p' getstream/models/__init__.py | head -50

echo ""
echo "=== ChannelInputRequest definition ==="
sed -n '/^class ChannelInputRequest/,/^class [A-Z]/p' getstream/models/__init__.py | head -50

# Identify method at line 260
echo ""
echo "=== Full method context at line 260 ==="
sed -n '240,280p' getstream/chat/rest_client.py | head -40

Repository: GetStream/stream-py

Length of output: 4897


Confirm type inconsistency across channel methods requires alignment.

The inconsistency is real: get_or_create_distinct_channel (line 178) and get_or_create_channel (line 525) use ChannelInput, while update_channel (line 260) uses ChannelInputRequest. The two types differ structurally—ChannelInput includes created_by_id, truncated_by_id, and filter_tags fields, while ChannelInputRequest uses ConfigOverridesRequest instead of ChannelConfig. Verify per the OpenAPI spec whether this distinction is intentional (e.g., create vs. update operations allow different fields) or if all three methods should use the same type.

Comment on lines 1592 to 1599
@telemetry.operation_name("getstream.api.feeds.query_feeds_usage_stats")
def query_feeds_usage_stats(
self, _from: Optional[str] = None, to: Optional[str] = None
) -> StreamResponse[QueryFeedsUsageStatsResponse]:
json = build_body_dict(_from=_from, to=to)
return self.post(
"/api/v2/feeds/stats/usage", QueryFeedsUsageStatsResponse, json=json
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the build_body_dict function definition
rg -n "def build_body_dict" --type py -A 10

Repository: GetStream/stream-py

Length of output: 1748


🏁 Script executed:

#!/bin/bash
# Search for other uses of _from in the codebase to understand the pattern
rg -n "_from" --type py getstream/ -B 2 -A 2 | head -60

Repository: GetStream/stream-py

Length of output: 4773


🏁 Script executed:

#!/bin/bash
# Check if there are API specs or documentation about the expected JSON keys
find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.txt" \) -exec grep -l "feeds.*usage.*stats\|query.*feeds.*usage\|_from\|from.*parameter" {} \;

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Read the build_body_dict implementation
sed -n '151,177p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 1033


🏁 Script executed:

#!/bin/bash
# Look for tests related to build_body_dict or query_feeds_usage_stats
find . -type f -name "*.py" -path "*/test*" -exec grep -l "build_body_dict\|query_feeds_usage_stats\|query_aggregate_call_stats" {} \;

Repository: GetStream/stream-py

Length of output: 87


🏁 Script executed:

#!/antml:bash
# Search for any place where the JSON key is explicitly set to "from" without underscore
rg -n '"from"' --type py getstream/ | head -20

Repository: GetStream/stream-py

Length of output: 310


🏁 Script executed:

#!/bin/bash
# Find the QueryFeedsUsageStatsResponse model and check its structure
rg -n "class QueryFeedsUsageStatsResponse" --type py -A 10

Repository: GetStream/stream-py

Length of output: 912


🏁 Script executed:

#!/bin/bash
# Search for any test files that test the query_feeds_usage_stats method
rg -n "query_feeds_usage_stats" --type py

Repository: GetStream/stream-py

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Look at the test_decoding.py file to understand the patterns
cat tests/test_decoding.py | head -100

Repository: GetStream/stream-py

Length of output: 3192


🏁 Script executed:

#!/bin/bash
# Search for tests that actually send requests with _from parameter
rg -n "from.*to" --type py tests/ -B 5 -A 5 | head -40

Repository: GetStream/stream-py

Length of output: 2608


🏁 Script executed:

#!/bin/bash
# Check if there are any comments or documentation about the API endpoint
rg -n "/api/v2/feeds/stats" --type py -B 5 -A 5

Repository: GetStream/stream-py

Length of output: 915


🏁 Script executed:

#!/antml:bash
# Look for other similar patterns where build_body_dict is used with keyword arguments
rg -n "build_body_dict\(" --type py -A 1 | head -40

Repository: GetStream/stream-py

Length of output: 2059


🏁 Script executed:

#!/an/bash
# Check if there's any request body transformation elsewhere
rg -n "field_name" --type py getstream/models/__init__.py | grep -i "from\|to" | head -20

Repository: GetStream/stream-py

Length of output: 1640


🏁 Script executed:

#!/bin/bash
# Look for any comments or patterns around reserved keywords in requests vs responses
rg -n "reserved\|keyword" --type py getstream/ | head -20

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/antml:bash
# Check how other Python clients handle this pattern - look for similar underscore-prefixed parameters
rg -n "def.*_from.*to.*Optional" --type py -A 5 | head -50

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for any explicit JSON mapping in request building
rg -n "\"from\"" --type py -B 3 -A 3 | head -40

Repository: GetStream/stream-py

Length of output: 1158


🏁 Script executed:

#!/an/bash
# Look at request/response examples if they exist in comments or docs
find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.json" \) -exec grep -l "feeds.*stats\|query.*usage" {} \;

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Check how the video client handles query_aggregate_call_stats - does it use build_body_dict or QueryAggregateCallStatsRequest?
sed -n '1241,1250p' getstream/video/rest_client.py

Repository: GetStream/stream-py

Length of output: 483


🏁 Script executed:

#!/an/bash
# Check if QueryAggregateCallStatsRequest is used anywhere or if build_body_dict is consistently used
rg -n "QueryAggregateCallStatsRequest\|QueryFeedsUsageStatsRequest" --type py

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Check the async version in feeds to see if it has the same pattern
rg -n "query_feeds_usage_stats" --type py -A 10 | head -30

Repository: GetStream/stream-py

Length of output: 1028


🏁 Script executed:

#!/an/bash
# Check if request models are instantiated and used in any request building
rg -n "QueryFeedsUsageStatsRequest\|QueryAggregateCallStatsRequest" --type py -B 2 -A 2

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Check if there are other methods that properly use request models for building request bodies
rg -n "to_dict\(\)" --type py getstream/feeds/rest_client.py -B 5 | head -40

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Check if DataClassJsonMixin has proper serialization that respects field_name
rg -n "DataClassJsonMixin" --type py getstream/models/__init__.py | head -5

Repository: GetStream/stream-py

Length of output: 282


🏁 Script executed:

#!/an/bash
# Look for examples of request model instantiation and to_dict usage
rg -n "\.to_dict\(\)" --type py getstream/ | head -20

Repository: GetStream/stream-py

Length of output: 129


🏁 Script executed:

#!/an/bash
# Check if DataClassJsonMixin.to_dict() respects field_name metadata
# Let's look at a test to see how DataClassJsonMixin serializes with field_name
rg -n "to_dict" --type py tests/ -B 5 -A 5 | head -50

Repository: GetStream/stream-py

Length of output: 1143


🏁 Script executed:

#!/an/bash
# Create a simple test to verify DataClassJsonMixin behavior with field_name
python3 << 'EOF'
from dataclasses import dataclass, field as dc_field
from dataclasses_json import DataClassJsonMixin, config as dc_config
from typing import Optional

# Simulate the QueryFeedsUsageStatsRequest structure
@dataclass
class TestRequest(DataClassJsonMixin):
    _from: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="from"))
    to: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="to"))

# Test with values
req = TestRequest(_from="2024-01-01", to="2024-12-31")
print("to_dict():", req.to_dict())

# Test with None values
req_none = TestRequest(_from=None, to=None)
print("to_dict() with None:", req_none.to_dict())

# Test to_json
print("to_json():", req.to_json())
EOF

Repository: GetStream/stream-py

Length of output: 149


🏁 Script executed:

#!/an/bash
# Simpler test without network - just check if dataclasses_json is available and test locally
python3 -c "
from dataclasses import dataclass, field as dc_field
from dataclasses_json import DataClassJsonMixin, config as dc_config
from typing import Optional

@dataclass
class TestRequest(DataClassJsonMixin):
    _from: Optional[str] = dc_field(default=None, metadata=dc_config(field_name='from'))
    to: Optional[str] = dc_field(default=None, metadata=dc_config(field_name='to'))

req = TestRequest(_from='2024-01-01', to='2024-12-31')
print('Result:', req.to_dict())
" 2>&1 || echo "Test failed or packages not available"

Repository: GetStream/stream-py

Length of output: 149


🏁 Script executed:

#!/an/bash
# Check if there's any pattern where request models are used for serialization
rg -n "QueryFeedsUsageStatsRequest\|QueryAggregateCallStatsRequest\|QueryCallStatsRequest" --type py -B 3 -A 3

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Check how the post method works - does it handle serialization?
sed -n '1,100p' getstream/feeds/rest_client.py | grep -A 20 "def post"

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Search for the post method in rest_client.py
rg -n "def post" --type py getstream/feeds/rest_client.py -A 10 | head -30

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Look at the parent class or base to understand post method
rg -n "class.*RestClient\|def post" --type py getstream/feeds/rest_client.py | head -10

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# Find where RestClient is defined
fd -e py -x grep -l "class RestClient" {} \;

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/an/bash
# List the files in getstream/feeds to understand structure
ls -la getstream/feeds/

Repository: GetStream/stream-py

Length of output: 422


🏁 Script executed:

#!/an/bash
# Check if rest_client.py has class definition
head -50 getstream/feeds/rest_client.py

Repository: GetStream/stream-py

Length of output: 1853


🏁 Script executed:

#!/an/bash
# Search for where the json parameter is used in post calls
rg -n "self.post" --type py getstream/feeds/rest_client.py -B 2 | head -40

Repository: GetStream/stream-py

Length of output: 1136


Use the QueryFeedsUsageStatsRequest model to properly serialize the _from parameter.

The build_body_dict() function produces {"_from": ...} but the API expects {"from": ...} based on the request model definition. The QueryFeedsUsageStatsRequest model already has the correct field mapping configured. Instantiate and serialize it instead:

Recommended fix
 @telemetry.operation_name("getstream.api.feeds.query_feeds_usage_stats")
 def query_feeds_usage_stats(
     self, _from: Optional[str] = None, to: Optional[str] = None
 ) -> StreamResponse[QueryFeedsUsageStatsResponse]:
-    json = build_body_dict(_from=_from, to=to)
+    request = QueryFeedsUsageStatsRequest(_from=_from, to=to)
+    json = request.to_dict()
     return self.post(
         "/api/v2/feeds/stats/usage", QueryFeedsUsageStatsResponse, json=json
     )

Note: The same issue exists in query_aggregate_call_stats in the video client.

🤖 Prompt for AI Agents
In @getstream/feeds/rest_client.py around lines 1592 - 1599, The current
query_feeds_usage_stats method constructs the request body with build_body_dict
which yields {"_from": ...} but the API expects the field mapped as "from"; fix
by creating a QueryFeedsUsageStatsRequest instance with _from and to, serialize
that model for the POST payload (replace the build_body_dict/json assignment
with model instantiation and model.dict()/serialize call per existing patterns),
ensuring the correct field name is sent; also apply the same change to the
query_aggregate_call_stats method in the video client.

Comment on lines 890 to 920
@telemetry.operation_name("getstream.api.video.get_call_stats_map")
async def get_call_stats_map(
self,
call_type: str,
call_id: str,
session: str,
start_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
exclude_publishers: Optional[bool] = None,
exclude_subscribers: Optional[bool] = None,
exclude_sfus: Optional[bool] = None,
) -> StreamResponse[QueryCallStatsMapResponse]:
query_params = build_query_param(
start_time=start_time,
end_time=end_time,
exclude_publishers=exclude_publishers,
exclude_subscribers=exclude_subscribers,
exclude_sfus=exclude_sfus,
)
path_params = {
"call_type": call_type,
"call_id": call_id,
"session": session,
}
return await self.get(
"/api/v2/video/call_stats/{call_type}/{call_id}/{session}/map",
QueryCallStatsMapResponse,
query_params=query_params,
path_params=path_params,
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find build_query_param implementation
rg -n "def build_query_param" --type py

Repository: GetStream/stream-py

Length of output: 203


🏁 Script executed:

#!/bin/bash
# Check the implementation of build_query_param for datetime handling
rg -A 30 "def build_query_param" getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 2651


🏁 Script executed:

#!/bin/bash
# Verify get_call_stats_map method exists and matches the snippet
rg -A 25 "async def get_call_stats_map" getstream/video/async_rest_client.py

Repository: GetStream/stream-py

Length of output: 1011


🏁 Script executed:

#!/bin/bash
# Get more lines from build_query_param to see the complete else clause
sed -n '94,120p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 1147


🏁 Script executed:

#!/bin/bash
# Also check if there's any import of datetime or datetime handling in utils
rg -n "datetime" getstream/utils/__init__.py | head -20

Repository: GetStream/stream-py

Length of output: 867


🏁 Script executed:

#!/bin/bash
# Get the complete build_query_param function including the else clause
sed -n '94,128p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 1432


Bug confirmed: datetime query params will cause TypeError in build_query_param.

get_call_stats_map passes start_time and end_time as datetime objects to build_query_param(), but the function doesn't handle datetime types. It falls through to json.dumps(value), which cannot serialize datetime objects by default, causing a TypeError at runtime.

Proposed fix (convert datetime to ISO-8601 before build_query_param)
@@
     async def get_call_stats_map(
@@
         exclude_sfus: Optional[bool] = None,
     ) -> StreamResponse[QueryCallStatsMapResponse]:
+        if isinstance(start_time, datetime):
+            start_time = start_time.isoformat()
+        if isinstance(end_time, datetime):
+            end_time = end_time.isoformat()
         query_params = build_query_param(
             start_time=start_time,
             end_time=end_time,
             exclude_publishers=exclude_publishers,
             exclude_subscribers=exclude_subscribers,
             exclude_sfus=exclude_sfus,
         )
🤖 Prompt for AI Agents
In @getstream/video/async_rest_client.py around lines 890 - 920,
get_call_stats_map currently forwards start_time and end_time datetime objects
to build_query_param which json.dumps can't serialize; convert start_time and
end_time to ISO-8601 strings (e.g., via start_time.isoformat()) before calling
build_query_param, or add a small helper to normalize datetimes to strings and
use it in get_call_stats_map so build_query_param only receives serializable
values; keep the rest of the method and path_params unchanged.

Comment on lines 886 to 916
@telemetry.operation_name("getstream.api.video.get_call_stats_map")
def get_call_stats_map(
self,
call_type: str,
call_id: str,
session: str,
start_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
exclude_publishers: Optional[bool] = None,
exclude_subscribers: Optional[bool] = None,
exclude_sfus: Optional[bool] = None,
) -> StreamResponse[QueryCallStatsMapResponse]:
query_params = build_query_param(
start_time=start_time,
end_time=end_time,
exclude_publishers=exclude_publishers,
exclude_subscribers=exclude_subscribers,
exclude_sfus=exclude_sfus,
)
path_params = {
"call_type": call_type,
"call_id": call_id,
"session": session,
}
return self.get(
"/api/v2/video/call_stats/{call_type}/{call_id}/{session}/map",
QueryCallStatsMapResponse,
query_params=query_params,
path_params=path_params,
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find build_query_param function definition and check its implementation
rg -n "def build_query_param" -A 20 getstream/

Repository: GetStream/stream-py

Length of output: 3140


🏁 Script executed:

#!/bin/bash
# Look for both sync and async client implementations of get_call_stats_map
rg -n "def get_call_stats_map" getstream/ -B 2 -A 15

Repository: GetStream/stream-py

Length of output: 2813


🏁 Script executed:

#!/bin/bash
# Check imports in rest_client.py to understand what modules are available
head -50 getstream/video/rest_client.py

Repository: GetStream/stream-py

Length of output: 1944


🏁 Script executed:

#!/bin/bash
# Get the complete build_query_param function implementation
sed -n '94,128p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 1432


🏁 Script executed:

#!/bin/bash
# Check imports and context in utils/__init__.py around build_query_param
head -100 getstream/utils/__init__.py | tail -50

Repository: GetStream/stream-py

Length of output: 1473


🏁 Script executed:

#!/bin/bash
# Search for custom JSON encoder or datetime handling in the codebase
rg -n "JSONEncoder" getstream/

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if datetime objects are actually used with build_query_param elsewhere
rg -n "datetime" getstream/video/rest_client.py | head -20

Repository: GetStream/stream-py

Length of output: 312


🏁 Script executed:

#!/bin/bash
# Find datetime_to_iso_string function and check its usage
rg -n "datetime_to_iso_string\|def.*iso" getstream/utils/__init__.py -B 2 -A 5

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check the full imports and beginning of utils/__init__.py
head -30 getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 741


🏁 Script executed:

#!/bin/bash
# Search for any datetime conversion or isoformat usage in rest_client.py
rg -n "isoformat\|strftime" getstream/video/rest_client.py

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if there's a test for get_call_stats_map to see if datetime is actually tested
rg -n "get_call_stats_map" getstream/ --type py -B 2 -A 10 | grep -A 10 "test\|Test"

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check what happens in json.dumps when datetime is passed - verify the actual error
python3 << 'EOF'
import json
from datetime import datetime

# Try to serialize a datetime object
dt = datetime.now()
try:
    result = json.dumps(dt)
    print(f"Success: {result}")
except TypeError as e:
    print(f"Error: {e}")
    print(f"Type: {type(dt)}")
EOF

Repository: GetStream/stream-py

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Check if datetime is being converted to ISO format before being passed to build_query_param
rg -n "start_time\|end_time" getstream/video/rest_client.py -B 3 -A 3 | grep -A 5 "get_call_stats_map"

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if there's a wrapper or conversion happening in the base client or get method
rg -n "def get\(" getstream/base.py -A 15 | head -50

Repository: GetStream/stream-py

Length of output: 1168


🏁 Script executed:

#!/bin/bash
# Check _request_sync and _request_async to see how query_params are handled
rg -n "_request_sync\|_request_async" getstream/base.py -A 30 | head -100

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look for where query_params are actually used in the request
rg -n "query_params" getstream/base.py -B 2 -A 2 | head -60

Repository: GetStream/stream-py

Length of output: 2121


🏁 Script executed:

#!/bin/bash
# Check _prepare_request to see full implementation
sed -n '98,160p' getstream/base.py

Repository: GetStream/stream-py

Length of output: 2143


🏁 Script executed:

#!/bin/bash
# Look for other methods that handle datetime parameters to see if there's a pattern
rg -n "datetime\|isoformat" getstream/video/rest_client.py -B 5 -A 2 | head -80

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check other similar methods in the async client to understand the pattern
rg -n "start_time\|end_time" getstream/video/async_rest_client.py -B 3 -A 8 | head -100

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for other query param methods to see if any convert datetime
rg -n "build_query_param" getstream/video/rest_client.py -B 10 | grep -A 10 "datetime\|start\|end" | head -80

Repository: GetStream/stream-py

Length of output: 793


🏁 Script executed:

#!/bin/bash
# Look at the complete get_call_stats method to see if there's datetime handling
rg -n "get_call_stats" getstream/video/rest_client.py -A 25 | head -100

Repository: GetStream/stream-py

Length of output: 1179


🏁 Script executed:

#!/bin/bash
# Check if datetime has isoformat method being called anywhere in the codebase
rg -n "\.isoformat\(\)" getstream/ --type py | head -20

Repository: GetStream/stream-py

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Check the isoformat function and surrounding code
sed -n '45,60p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 412


🏁 Script executed:

#!/bin/bash
# Search for where datetime objects should be converted to ISO format
rg -n "datetime_to_iso_string\|to_iso\|\.isoformat" getstream/utils/__init__.py -B 3 -A 3

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look for any conversion of datetime in the entire getstream directory
rg -n "strftime\|isoformat" getstream/ --type py

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Find the complete function name for datetime conversion
sed -n '40,55p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 443


🏁 Script executed:

#!/bin/bash
# Check if datetime_to_iso_string is exported and used
rg -n "datetime_to_iso\|datetime_to" getstream/utils/__init__.py -B 2 -A 5

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check what's exported from utils/__init__.py
rg -n "__all__\|^def " getstream/utils/__init__.py | head -30

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Find the function definition for datetime encoding
sed -n '35,55p' getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 593


🏁 Script executed:

#!/bin/bash
# Check imports in rest_client.py to see if datetime_to_iso_string or similar is imported
head -20 getstream/video/rest_client.py

Repository: GetStream/stream-py

Length of output: 695


🏁 Script executed:

#!/bin/bash
# Test what happens when we pass a datetime to build_query_param
python3 << 'EOF'
import sys
sys.path.insert(0, '/tmp/repo')

from datetime import datetime
import json

# Simulate build_query_param behavior
def build_query_param(**kwargs):
    params = {}
    for key, value in kwargs.items():
        if value is None:
            continue
        if hasattr(value, "to_json") and callable(value.to_json):
            params[key] = value.to_json()
        elif isinstance(value, bool):
            params[key] = str(value).lower()
        elif isinstance(value, (str, int)):
            params[key] = str(value)
        elif isinstance(value, list):
            from urllib.parse import quote
            params[key] = ",".join(quote(str(v)) for v in value)
        else:
            # For dictionaries or any other types of objects
            params[key] = json.dumps(value)
    return params

# Test with datetime
dt = datetime.now()
try:
    result = build_query_param(start_time=dt)
    print(f"Success: {result}")
except TypeError as e:
    print(f"Error: {e}")
    print("This confirms datetime objects will cause json.dumps() to fail")
EOF

Repository: GetStream/stream-py

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Check if encode_datetime is used anywhere in the codebase
rg -n "encode_datetime" getstream/ --type py

Repository: GetStream/stream-py

Length of output: 45004


🏁 Script executed:

#!/bin/bash
# Check other methods in rest_client.py that have datetime parameters to see if they handle it
rg -n "def.*datetime" getstream/video/rest_client.py -A 15 | head -100

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for any method that properly handles datetime in query params
rg -n "start_time\|end_time" getstream/ --type py -B 5 -A 10 | grep -B 5 -A 5 "encode_datetime\|isoformat" | head -80

Repository: GetStream/stream-py

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if build_query_param needs to import and use encode_datetime
grep -n "encode_datetime" getstream/utils/__init__.py

Repository: GetStream/stream-py

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Verify the issue by checking the actual build_query_param implementation one more time
# to confirm datetime is NOT handled
sed -n '94,128p' getstream/utils/__init__.py | grep -n "datetime"

Repository: GetStream/stream-py

Length of output: 45


build_query_param does not handle datetime objects—passes to json.dumps() and will raise TypeError.

Both get_call_stats_map in sync (rest_client.py lines 887–916) and async (async_rest_client.py lines 891–906) clients accept datetime parameters (start_time, end_time) and pass them directly to build_query_param. The function falls through to json.dumps(value) for unhandled types, which cannot serialize datetime objects and will raise TypeError: Object of type datetime is not JSON serializable. The codebase has an encode_datetime utility function for ISO serialization, but it is not integrated into build_query_param.

🤖 Prompt for AI Agents
In @getstream/video/rest_client.py around lines 886 - 916, The query builder is
serializing datetime via json.dumps and fails; update build_query_param to
detect datetime (and datetime-like) values and serialize them using the existing
encode_datetime utility (or call encode_datetime for the start_time/end_time
before passing) so ISO strings are produced; ensure build_query_param handles
both naive and aware datetimes and lists/dicts containing datetimes so calls
from get_call_stats_map (and its async counterpart) will no longer raise
TypeError.

@mogita mogita changed the title refactor: code generated from refactored openapi specs [CHA-1578] refactor: code generated from openapi specs Feb 16, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@getstream/tests/test_webhook.py`:
- Around line 77-716: Tests call parse_webhook_event with payloads that lack
required dataclass fields (e.g., CustomEvent requires created_at and custom;
MessageNewEvent requires message_id, watcher_count, message), causing TypeError
when event_class(**data) in parse_webhook_event is invoked; fix by updating the
test payloads passed to parse_webhook_event to include all mandatory fields for
each event class (populate created_at with an ISO datetime string or datetime
object, include a non-empty custom dict for CustomEvent, and add message_id,
watcher_count, message for MessageNewEvent, etc.) so that instantiation of the
dataclasses (via parse_webhook_event) succeeds.
🧹 Nitpick comments (1)
getstream/webhook.py (1)

387-550: event_map dict is rebuilt on every call.

_get_event_class allocates a ~130-entry dict each invocation. Since this is generated code the impact is low, but moving the map to module scope would be a trivial improvement.

♻️ Suggested refactor
+_EVENT_MAP = {
+    "*": CustomEvent,
+    "appeal.accepted": AppealAcceptedEvent,
+    # ... rest of entries ...
+}
+
 def _get_event_class(event_type: str):
     """Map event type to event class."""
-    event_map = {
-        "*": CustomEvent,
-        ...
-    }
-    return event_map.get(event_type)
+    return _EVENT_MAP.get(event_type)

@mogita
Copy link
Author

mogita commented Feb 17, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
getstream/chat/async_rest_client.py (1)

1629-1640: ⚠️ Potential issue | 🔴 Critical

query_segment_targets uses "Sort" / "Filter" keys (likely wrong).

At Line 1637-1638 the request body uses "Sort" and "Filter", while the method parameters are sort and filter and other endpoints use lowercase keys. Unless the API explicitly expects capitalized keys, this will silently drop the parameters server-side.

Proposed fix
 json = build_body_dict(
     **{
         "limit": limit,
         "next": next,
         "prev": prev,
-        "Sort": sort,
-        "Filter": filter,
+        "sort": sort,
+        "filter": filter,
     }
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/chat/async_rest_client.py` around lines 1629 - 1640, The request
body in query_segment_targets is using capitalized keys "Sort" and "Filter"
instead of the lowercase parameter names, which will drop the parameters; update
the build_body_dict call in query_segment_targets to pass "sort": sort and
"filter": filter (matching the method parameters and other endpoints) so the
payload uses the correct keys and values.
tests/test_video_examples.py (1)

391-395: ⚠️ Potential issue | 🔴 Critical

CI is failing due to rate limiting in test_fps (429 at Line 392).

Given the unit pipeline failure, this test should be guarded (e.g., @pytest.mark.skip_in_ci) or skip when a 429 is encountered.

Minimal CI-stabilizing change
+@pytest.mark.skip_in_ci
 def test_fps(client: Stream):
     response = client.video.get_active_calls_status()
     resolution = response.data.metrics.publishers.all.video.resolution
     print(resolution.p10)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_video_examples.py` around lines 391 - 395, The test_fps test is
flaky in CI due to rate limiting (HTTP 429) from
client.video.get_active_calls_status(); modify test_fps to either decorate it
with `@pytest.mark.skip_in_ci` or wrap the call in a try/except that catches the
429 error (e.g., the HTTP error or client-specific RateLimitError) and calls
pytest.skip when encountered; ensure you reference the test function name
test_fps and the call client.video.get_active_calls_status() so the
guard/exception handling is applied only to this test.
getstream/moderation/async_rest_client.py (1)

437-449: ⚠️ Potential issue | 🔴 Critical

Fix missing {id} path parameter for moderation rule get/delete endpoints.

"/api/v2/moderation/moderation_rule/{id}" is used in both methods, but neither method accepts an id nor passes path_params. This will fail at runtime (Line 442 / Line 448).

Proposed fix
 `@telemetry.operation_name`("getstream.api.moderation.delete_moderation_rule")
 async def delete_moderation_rule(
-    self,
+    self,
+    id: str,
 ) -> StreamResponse[DeleteModerationRuleResponse]:
-    return await self.delete(
-        "/api/v2/moderation/moderation_rule/{id}", DeleteModerationRuleResponse
-    )
+    path_params = {"id": id}
+    return await self.delete(
+        "/api/v2/moderation/moderation_rule/{id}",
+        DeleteModerationRuleResponse,
+        path_params=path_params,
+    )

 `@telemetry.operation_name`("getstream.api.moderation.get_moderation_rule")
-async def get_moderation_rule(self) -> StreamResponse[GetModerationRuleResponse]:
-    return await self.get(
-        "/api/v2/moderation/moderation_rule/{id}", GetModerationRuleResponse
-    )
+async def get_moderation_rule(self, id: str) -> StreamResponse[GetModerationRuleResponse]:
+    path_params = {"id": id}
+    return await self.get(
+        "/api/v2/moderation/moderation_rule/{id}",
+        GetModerationRuleResponse,
+        path_params=path_params,
+    )

If these files are regenerated in CI, this likely needs to be fixed in the generator templates/spec too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/moderation/async_rest_client.py` around lines 437 - 449, Both
delete_moderation_rule and get_moderation_rule are calling the path
"/api/v2/moderation/moderation_rule/{id}" but neither accepts an id nor supplies
path_params; update the two methods (delete_moderation_rule and
get_moderation_rule) to accept an id parameter (e.g., id: str or int) and pass
it as path_params={'id': id} in the call to self.delete/self.get so the {id}
placeholder is resolved at runtime; ensure the parameter name matches the
placeholder and keep the return types DeleteModerationRuleResponse and
GetModerationRuleResponse unchanged.
🧹 Nitpick comments (5)
getstream/utils/__init__.py (1)

142-161: Async docstring doesn't mention datetime support.

The sync version's docstring was updated (line 111) to note datetime handling, but this async counterpart still omits it. Since build_query_param_async delegates to build_query_param, the same capabilities apply.

📝 Suggested docstring update
     This function handles various data types:
     - JSON-serializable objects with a `to_json` method will be serialized using that method.
+    - datetime objects are converted to ISO 8601 strings.
     - Booleans are converted to lowercase strings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/utils/__init__.py` around lines 142 - 161, The async function
build_query_param_async delegates to build_query_param and therefore also
supports datetime objects; update its docstring to mention datetime handling
(e.g., that datetime objects are serialized/handled the same way as described in
build_query_param), mirroring the sync version's note about datetime support so
the async docstring accurately reflects supported types and behavior when
offloading to asyncio.to_thread.
getstream/common/rest_client.py (1)

1018-1042: CI rate-limit on delete_users suggests retry/backoff or test isolation.
The pipeline shows HTTP 429 for this call. Consider adding client-side retry/backoff for 429s or ensuring CI uses stubbed/mocked responses to avoid flakiness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/common/rest_client.py` around lines 1018 - 1042, Tests are flaking
due to HTTP 429s from delete_users; add client-side retry with exponential
backoff and jitter for 429 responses in the delete_users call path (or use the
client's central HTTP call wrapper if one exists) so CI doesn't fail
intermittently. Modify the delete_users method to catch 429s, retry up to a
small max (e.g., 3-5 attempts) with increasing delays and random jitter, and
surface the final error if all attempts fail; alternatively, wire delete_users
to an existing retry-enabled request helper in this module so the retry logic is
centralized and reusable.
getstream/tests/test_webhook.py (1)

79-1043: Parametrize the event-type mapping tests to reduce repetition.

TestParseWebhookEvent is essentially the same test repeated for many event types. A @pytest.mark.parametrize table (event_type → expected class) would keep coverage while shrinking the file and making additions/removals trivial.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/tests/test_webhook.py` around lines 79 - 1043, Replace the many
near-duplicate test_* methods in TestParseWebhookEvent with a single
parametrized pytest test that calls parse_webhook_event for each event type and
asserts the returned object's class name; create a mapping/list of tuples
(event_type, expected_class_name) and decorate one function (e.g.,
test_parse_event_mapping) with `@pytest.mark.parametrize` to iterate them, keep
the warnings.catch_warnings block and assertion logic identical to existing
tests so parse_webhook_event and the expected class names (e.g.,
"CallAcceptedEvent", "ChannelCreatedEvent", etc.) are used unchanged.
getstream/webhook.py (1)

355-390: Expose infer_missing as a configurable kw-only parameter to allow stricter payload validation.

The current implementation unconditionally uses infer_missing=True (line 387), which sets missing required fields to None instead of raising an error. This can mask incomplete webhook payloads. Expose this as a kw-only parameter with the current behavior as default to maintain backward compatibility while allowing callers to opt into strict parsing.

Proposed change
-def parse_webhook_event(raw_event: Union[bytes, str, Dict[str, Any]]) -> Any:
+def parse_webhook_event(
+    raw_event: Union[bytes, str, Dict[str, Any]], *, infer_missing: bool = True
+) -> Any:
@@
-    try:
-        return event_class.from_dict(data, infer_missing=True)
+    try:
+        return event_class.from_dict(data, infer_missing=infer_missing)
     except Exception as e:
         raise ValueError(f"Failed to deserialize webhook event: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/webhook.py` around lines 355 - 390, Update parse_webhook_event to
accept a keyword-only parameter infer_missing (default True) so callers can opt
into strict validation; change the signature to include *, infer_missing: bool =
True, document the new parameter in the docstring, and pass infer_missing
through to event_class.from_dict(data, infer_missing=infer_missing) instead of
hardcoding True (keep current defaults/behavior for backward compatibility).
getstream/chat/rest_client.py (1)

95-109: schedule_campaign sends an empty {} JSON body.

build_body_dict(**{}) produces {}. Same pattern as stop_recording and commit_message. If the backend requires no body for this endpoint, consider omitting json= entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/chat/rest_client.py` around lines 95 - 109, The schedule_campaign
method currently constructs an empty JSON body via build_body_dict(**{}) and
passes it to self.post; if the /api/v2/chat/campaigns/{id}/stop endpoint
requires no request body, remove the empty body by deleting the call to
build_body_dict and the json= parameter from the self.post invocation (i.e.,
call self.post(...) without json) so schedule_campaign no longer sends {} —
locate the schedule_campaign function and its use of build_body_dict and
self.post to make this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@getstream/chat/rest_client.py`:
- Around line 1622-1630: The body dict passed to build_body_dict uses PascalCase
keys "Sort" and "Filter" which should be lowercase; update the dictionary
entries (the build_body_dict call that currently maps "Sort": sort and "Filter":
filter) to use "sort": sort and "filter": filter so the server receives them,
and mirror the same change in the async client counterpart
(async_rest_client.py) where the same PascalCase keys appear.

---

Outside diff comments:
In `@getstream/chat/async_rest_client.py`:
- Around line 1629-1640: The request body in query_segment_targets is using
capitalized keys "Sort" and "Filter" instead of the lowercase parameter names,
which will drop the parameters; update the build_body_dict call in
query_segment_targets to pass "sort": sort and "filter": filter (matching the
method parameters and other endpoints) so the payload uses the correct keys and
values.

In `@getstream/moderation/async_rest_client.py`:
- Around line 437-449: Both delete_moderation_rule and get_moderation_rule are
calling the path "/api/v2/moderation/moderation_rule/{id}" but neither accepts
an id nor supplies path_params; update the two methods (delete_moderation_rule
and get_moderation_rule) to accept an id parameter (e.g., id: str or int) and
pass it as path_params={'id': id} in the call to self.delete/self.get so the
{id} placeholder is resolved at runtime; ensure the parameter name matches the
placeholder and keep the return types DeleteModerationRuleResponse and
GetModerationRuleResponse unchanged.

In `@tests/test_video_examples.py`:
- Around line 391-395: The test_fps test is flaky in CI due to rate limiting
(HTTP 429) from client.video.get_active_calls_status(); modify test_fps to
either decorate it with `@pytest.mark.skip_in_ci` or wrap the call in a try/except
that catches the 429 error (e.g., the HTTP error or client-specific
RateLimitError) and calls pytest.skip when encountered; ensure you reference the
test function name test_fps and the call client.video.get_active_calls_status()
so the guard/exception handling is applied only to this test.

---

Duplicate comments:
In `@getstream/chat/rest_client.py`:
- Line 276: The update_channel signature uses ChannelInputRequest while
get_or_create_distinct_channel and get_or_create_channel use ChannelInput,
causing a type inconsistency; standardize on one type (prefer ChannelInput for
consistency) by changing the update_channel parameter type from
ChannelInputRequest to ChannelInput (or convert ChannelInputRequest usages to
ChannelInput where appropriate), update any internal references inside
update_channel to match ChannelInput fields, and ensure all imports and type
hints (in update_channel, get_or_create_distinct_channel, get_or_create_channel)
are aligned to the chosen type to avoid mismatches.

In `@getstream/video/rest_client.py`:
- Around line 1066-1080: query_call_session_participant_stats is passing sort:
List[SortParamRequest] into build_query_param which currently stringifies list
items via str(v) instead of serializing them with each
SortParamRequest.to_json(); update the serialization so build_query_param (or
the call site) maps the sort list to [s.to_json() for s in sort] before passing
it (or adjust build_query_param to detect SortParamRequest instances and call
to_json()), ensuring SortParamRequest objects are correctly converted to JSON
for query string construction.
- Around line 994-1025: get_call_stats_map (and
get_call_participant_session_metrics) currently passes datetime objects
(start_time/end_time and since/until) into build_query_param which will end up
calling json.dumps and raise TypeError; convert any datetime values to an
ISO8601 string (e.g., using .isoformat()) before calling build_query_param or
enhance build_query_param to detect datetime and serialize to ISO strings;
update the calls in get_call_stats_map and get_call_participant_session_metrics
to pass the serialized string(s) (or adjust build_query_param to handle datetime
types) so no raw datetime reaches json.dumps.
- Around line 516-557: The stop_recording implementation currently calls
build_body_dict(**{}) which sends an empty JSON object; change stop_recording to
not send an empty body—either omit the json argument in the self.post call or
pass json=None so no {} is sent. Locate stop_recording (and build_body_dict
usage) and update the self.post invocation for the
"/api/v2/video/call/{type}/{id}/recordings/{recording_type}/stop" endpoint to
call self.post(..., path_params=path_params) without json (or with json=None)
instead of json=build_body_dict(**{}).

---

Nitpick comments:
In `@getstream/chat/rest_client.py`:
- Around line 95-109: The schedule_campaign method currently constructs an empty
JSON body via build_body_dict(**{}) and passes it to self.post; if the
/api/v2/chat/campaigns/{id}/stop endpoint requires no request body, remove the
empty body by deleting the call to build_body_dict and the json= parameter from
the self.post invocation (i.e., call self.post(...) without json) so
schedule_campaign no longer sends {} — locate the schedule_campaign function and
its use of build_body_dict and self.post to make this change.

In `@getstream/common/rest_client.py`:
- Around line 1018-1042: Tests are flaking due to HTTP 429s from delete_users;
add client-side retry with exponential backoff and jitter for 429 responses in
the delete_users call path (or use the client's central HTTP call wrapper if one
exists) so CI doesn't fail intermittently. Modify the delete_users method to
catch 429s, retry up to a small max (e.g., 3-5 attempts) with increasing delays
and random jitter, and surface the final error if all attempts fail;
alternatively, wire delete_users to an existing retry-enabled request helper in
this module so the retry logic is centralized and reusable.

In `@getstream/tests/test_webhook.py`:
- Around line 79-1043: Replace the many near-duplicate test_* methods in
TestParseWebhookEvent with a single parametrized pytest test that calls
parse_webhook_event for each event type and asserts the returned object's class
name; create a mapping/list of tuples (event_type, expected_class_name) and
decorate one function (e.g., test_parse_event_mapping) with
`@pytest.mark.parametrize` to iterate them, keep the warnings.catch_warnings block
and assertion logic identical to existing tests so parse_webhook_event and the
expected class names (e.g., "CallAcceptedEvent", "ChannelCreatedEvent", etc.)
are used unchanged.

In `@getstream/utils/__init__.py`:
- Around line 142-161: The async function build_query_param_async delegates to
build_query_param and therefore also supports datetime objects; update its
docstring to mention datetime handling (e.g., that datetime objects are
serialized/handled the same way as described in build_query_param), mirroring
the sync version's note about datetime support so the async docstring accurately
reflects supported types and behavior when offloading to asyncio.to_thread.

In `@getstream/webhook.py`:
- Around line 355-390: Update parse_webhook_event to accept a keyword-only
parameter infer_missing (default True) so callers can opt into strict
validation; change the signature to include *, infer_missing: bool = True,
document the new parameter in the docstring, and pass infer_missing through to
event_class.from_dict(data, infer_missing=infer_missing) instead of hardcoding
True (keep current defaults/behavior for backward compatibility).

Comment on lines 1622 to 1630
json = build_body_dict(
limit=limit, next=next, prev=prev, sort=sort, filter=filter
**{
"limit": limit,
"next": next,
"prev": prev,
"Sort": sort,
"Filter": filter,
}
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Step 1: Verify PascalCase "Sort" and "Filter" usage
echo "=== Checking for PascalCase Sort/Filter ==="
rg -n '"Sort"|"Filter"' getstream/chat/rest_client.py getstream/chat/async_rest_client.py

echo ""
echo "=== Checking for lowercase sort/filter in rest_client.py ==="
rg -n '"sort"|"filter"' getstream/chat/rest_client.py | head -30

Repository: GetStream/stream-py

Length of output: 1029


🏁 Script executed:

#!/bin/bash
# Verify the method name and context for the PascalCase issue
echo "=== rest_client.py lines 1620-1635 ==="
sed -n '1620,1635p' getstream/chat/rest_client.py

echo ""
echo "=== async_rest_client.py lines 1630-1645 ==="
sed -n '1630,1645p' getstream/chat/async_rest_client.py

Repository: GetStream/stream-py

Length of output: 1039


"Sort" and "Filter" keys are PascalCase — a code-generation bug affecting both sync and async clients.

Every other method in this file uses lowercase "sort" and "filter" for these body keys. The PascalCase variants at lines 1627–1628 (rest_client.py) and 1637–1638 (async_rest_client.py) will cause the server to silently ignore the sort and filter parameters, breaking query_segment_targets.

Fix
         json = build_body_dict(
             **{
                 "limit": limit,
                 "next": next,
                 "prev": prev,
-                "Sort": sort,
-                "Filter": filter,
+                "sort": sort,
+                "filter": filter,
             }
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/chat/rest_client.py` around lines 1622 - 1630, The body dict passed
to build_body_dict uses PascalCase keys "Sort" and "Filter" which should be
lowercase; update the dictionary entries (the build_body_dict call that
currently maps "Sort": sort and "Filter": filter) to use "sort": sort and
"filter": filter so the server receives them, and mirror the same change in the
async client counterpart (async_rest_client.py) where the same PascalCase keys
appear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant