getstream 7.2.0__tar.gz → 8.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {getstream-7.2.0 → getstream-8.1.0}/.release-please-manifest.json +1 -1
  2. {getstream-7.2.0 → getstream-8.1.0}/CHANGELOG.md +33 -3
  3. getstream-8.1.0/MIGRATION_v7_to_v8.md +114 -0
  4. {getstream-7.2.0 → getstream-8.1.0}/PKG-INFO +1 -1
  5. {getstream-7.2.0 → getstream-8.1.0}/getstream/base.py +19 -13
  6. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/async_channel.py +96 -1
  7. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/async_client.py +3 -37
  8. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/async_rest_client.py +367 -88
  9. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/channel.py +96 -1
  10. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/client.py +3 -37
  11. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/rest_client.py +365 -88
  12. getstream-8.1.0/getstream/common/async_client.py +12 -0
  13. {getstream-7.2.0 → getstream-8.1.0}/getstream/common/async_rest_client.py +138 -44
  14. getstream-8.1.0/getstream/common/client.py +12 -0
  15. {getstream-7.2.0 → getstream-8.1.0}/getstream/common/rest_client.py +134 -44
  16. {getstream-7.2.0 → getstream-8.1.0}/getstream/feeds/feeds.py +23 -0
  17. {getstream-7.2.0 → getstream-8.1.0}/getstream/feeds/rest_client.py +335 -67
  18. {getstream-7.2.0 → getstream-8.1.0}/getstream/models/__init__.py +10641 -5477
  19. {getstream-7.2.0 → getstream-8.1.0}/getstream/moderation/async_rest_client.py +291 -23
  20. {getstream-7.2.0 → getstream-8.1.0}/getstream/moderation/rest_client.py +291 -23
  21. {getstream-7.2.0 → getstream-8.1.0}/getstream/tests/test_webhook.py +126 -10
  22. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/async_call.py +9 -2
  23. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/async_rest_client.py +50 -20
  24. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/call.py +9 -2
  25. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rest_client.py +50 -20
  26. {getstream-7.2.0 → getstream-8.1.0}/getstream/webhook.py +114 -57
  27. {getstream-7.2.0 → getstream-8.1.0}/pyproject.toml +1 -1
  28. {getstream-7.2.0 → getstream-8.1.0}/uv.lock +1 -1
  29. getstream-7.2.0/getstream/common/async_client.py +0 -58
  30. getstream-7.2.0/getstream/common/client.py +0 -58
  31. {getstream-7.2.0 → getstream-8.1.0}/.cursor/worktrees.json +0 -0
  32. {getstream-7.2.0 → getstream-8.1.0}/.env.example +0 -0
  33. {getstream-7.2.0 → getstream-8.1.0}/.github/actions/build-rtc-wheel/action.yml +0 -0
  34. {getstream-7.2.0 → getstream-8.1.0}/.github/actions/python-uv-setup/action.yml +0 -0
  35. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/ci.yml +0 -0
  36. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/pr_title.yml +0 -0
  37. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/release.yml +0 -0
  38. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/rtc_wheels.yml +0 -0
  39. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/run_integration.yml +0 -0
  40. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/run_tests.yml +0 -0
  41. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/scheduled_test.yml +0 -0
  42. {getstream-7.2.0 → getstream-8.1.0}/.github/workflows/stream-py.code-workspace +0 -0
  43. {getstream-7.2.0 → getstream-8.1.0}/.gitignore +0 -0
  44. {getstream-7.2.0 → getstream-8.1.0}/.gitmodules +0 -0
  45. {getstream-7.2.0 → getstream-8.1.0}/.pre-commit-config.yaml +0 -0
  46. {getstream-7.2.0 → getstream-8.1.0}/AGENTS.md +0 -0
  47. {getstream-7.2.0 → getstream-8.1.0}/DEVELOPMENT.md +0 -0
  48. {getstream-7.2.0 → getstream-8.1.0}/LICENSE.md +0 -0
  49. {getstream-7.2.0 → getstream-8.1.0}/MIGRATION_v2_to_v3.md +0 -0
  50. {getstream-7.2.0 → getstream-8.1.0}/MIGRATION_v6_to_v7.md +0 -0
  51. {getstream-7.2.0 → getstream-8.1.0}/Makefile +0 -0
  52. {getstream-7.2.0 → getstream-8.1.0}/README.md +0 -0
  53. {getstream-7.2.0 → getstream-8.1.0}/dev.py +0 -0
  54. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/01-setup-and-auth.md +0 -0
  55. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/02-users.md +0 -0
  56. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/03-channels.md +0 -0
  57. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/04-messages-and-reactions.md +0 -0
  58. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/05-moderation.md +0 -0
  59. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/06-devices.md +0 -0
  60. {getstream-7.2.0 → getstream-8.1.0}/docs/migration-from-stream-chat-python/README.md +0 -0
  61. {getstream-7.2.0 → getstream-8.1.0}/generate.sh +0 -0
  62. {getstream-7.2.0 → getstream-8.1.0}/generate_webrtc.sh +0 -0
  63. {getstream-7.2.0 → getstream-8.1.0}/getstream/__init__.py +0 -0
  64. {getstream-7.2.0 → getstream-8.1.0}/getstream/chat/__init__.py +0 -0
  65. {getstream-7.2.0 → getstream-8.1.0}/getstream/common/__init__.py +0 -0
  66. {getstream-7.2.0 → getstream-8.1.0}/getstream/common/telemetry.py +0 -0
  67. {getstream-7.2.0 → getstream-8.1.0}/getstream/config.py +0 -0
  68. {getstream-7.2.0 → getstream-8.1.0}/getstream/exceptions.py +0 -0
  69. {getstream-7.2.0 → getstream-8.1.0}/getstream/feeds/__init__.py +0 -0
  70. {getstream-7.2.0 → getstream-8.1.0}/getstream/feeds/client.py +0 -0
  71. {getstream-7.2.0 → getstream-8.1.0}/getstream/generic.py +0 -0
  72. {getstream-7.2.0 → getstream-8.1.0}/getstream/logging_utils.py +0 -0
  73. {getstream-7.2.0 → getstream-8.1.0}/getstream/meta.py +0 -0
  74. {getstream-7.2.0 → getstream-8.1.0}/getstream/moderation/__init__.py +0 -0
  75. {getstream-7.2.0 → getstream-8.1.0}/getstream/moderation/async_client.py +0 -0
  76. {getstream-7.2.0 → getstream-8.1.0}/getstream/moderation/client.py +0 -0
  77. {getstream-7.2.0 → getstream-8.1.0}/getstream/rate_limit.py +0 -0
  78. {getstream-7.2.0 → getstream-8.1.0}/getstream/stream.py +0 -0
  79. {getstream-7.2.0 → getstream-8.1.0}/getstream/stream_response.py +0 -0
  80. {getstream-7.2.0 → getstream-8.1.0}/getstream/tasks.py +0 -0
  81. {getstream-7.2.0 → getstream-8.1.0}/getstream/utils/__init__.py +0 -0
  82. {getstream-7.2.0 → getstream-8.1.0}/getstream/utils/event_emitter.py +0 -0
  83. {getstream-7.2.0 → getstream-8.1.0}/getstream/version.py +0 -0
  84. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/__init__.py +0 -0
  85. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/async_client.py +0 -0
  86. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/client.py +0 -0
  87. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/openai.py +0 -0
  88. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/__init__.py +0 -0
  89. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/audio_forwarder.py +0 -0
  90. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/audio_track.py +0 -0
  91. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/connection_manager.py +0 -0
  92. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/g711.py +0 -0
  93. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/participants.py +0 -0
  94. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/__init__.py +0 -0
  95. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/__init__.py +0 -0
  96. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/__init__.py +0 -0
  97. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/__init__.py +0 -0
  98. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py +0 -0
  99. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py +0 -0
  100. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi +0 -0
  101. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py +0 -0
  102. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py +0 -0
  103. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi +0 -0
  104. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py +0 -0
  105. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py +0 -0
  106. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.pyi +0 -0
  107. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_twirp.py +0 -0
  108. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/track_util.py +0 -0
  109. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/tracks.py +0 -0
  110. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/utils.py +0 -0
  111. {getstream-7.2.0 → getstream-8.1.0}/getstream/video/rtc/video_forwarder.py +0 -0
  112. {getstream-7.2.0 → getstream-8.1.0}/pytest.ini +0 -0
  113. {getstream-7.2.0 → getstream-8.1.0}/release-please-config.json +0 -0
@@ -1,4 +1,4 @@
1
1
  {
2
- ".": "7.2.0",
2
+ ".": "8.1.0",
3
3
  "getstream-rtc": "0.1.0"
4
4
  }
@@ -7,17 +7,47 @@ Entries from 6.1.1 on are generated by release-please from commit messages; earl
7
7
  ones were written by hand in the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
8
8
  format.
9
9
 
10
- ## [7.2.0](https://github.com/GetStream/stream-py/compare/v7.1.0...v7.2.0) (2026-11-12)
10
+ ## [8.1.0](https://github.com/GetStream/stream-py/compare/v8.0.0...v8.1.0) (2026-11-12)
11
11
 
12
12
 
13
13
  ### Features
14
14
 
15
- * **moderation:** add `expected_value` to `UserCustomPropertyParameters`, `CallCustomPropertyParameters`, `ContentCustomPropertyParameters` and `ContentCustomPropertyCountParameters`, so value-operator custom-property rules can be set ([138e50a](https://github.com/GetStream/stream-py/commit/138e50ad2a713a85eef61508d6cf854d64692460))
15
+ * **moderation:** add `count_review_queue` to count the review queue items that match a filter ([6fce09c](https://github.com/GetStream/stream-py/commit/6fce09c8c38dc555c9a5de2bd701a312cc177a7e))
16
+ * **moderation:** add `expected_value` to `UserCustomPropertyParameters`, `CallCustomPropertyParameters`, `ContentCustomPropertyParameters` and `ContentCustomPropertyCountParameters`, so value-operator custom-property rules can be set ([6fce09c](https://github.com/GetStream/stream-py/commit/6fce09c8c38dc555c9a5de2bd701a312cc177a7e))
17
+ * **moderation:** add agent review: `submit_agent_feedback`, `list_agent_feedback` and `get_agent_review_status`, plus `agent_review` on `update_app` and `agent_recommendation` on `submit_action` ([6fce09c](https://github.com/GetStream/stream-py/commit/6fce09c8c38dc555c9a5de2bd701a312cc177a7e))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * cap decompressed gzip webhook bodies at 10 MiB (`getstream.webhook.MAX_WEBHOOK_PAYLOAD_BYTES`) and reject data after the gzip stream, because decompression runs before the signature check ([6fce09c](https://github.com/GetStream/stream-py/commit/6fce09c8c38dc555c9a5de2bd701a312cc177a7e))
23
+
24
+ ## [8.0.0](https://github.com/GetStream/stream-py/compare/v7.1.0...v8.0.0) (2026-11-11)
25
+
26
+
27
+ ### ⚠ BREAKING CHANGES
28
+
29
+ * new optional parameters sit in the middle of many method signatures, and a few models and return types changed. See [#313](https://github.com/GetStream/stream-py/pull/313) and the [migration guide](https://github.com/GetStream/stream-py/blob/main/MIGRATION_v7_to_v8.md) for all changes. The main changes:
30
+ * pass optional parameters by keyword: 41 methods, among them `send_message`, `query_channels`, `get_or_create_channel`, `ban`, `unban`, `get_activity` and the poll methods, have new parameters before existing ones
31
+ * `update_channel_type` makes `automod`, `automod_behavior` and `max_message_length` optional, and `submit_moderation_feedback` makes `message`, `published_at` and `reference` optional; both move them behind the required parameters
32
+ * `update_campaign` returns `UpdateCampaignResponse` and `stop_campaign` returns `StopCampaignResponse`; read the campaign from `.campaign`
33
+ * the empty request models `CancelImportV2TaskRequest`, `CommitMessageRequest`, `EndCallRequest`, `ExportFeedUserDataRequest`, `RestoreFeedGroupRequest`, `StartHLSBroadcastingRequest`, `StartPolicyTestRunRequest`, `StopAllRTMPBroadcastsRequest`, `StopCampaignRequest`, `StopFrameRecordingRequest`, `StopHLSBroadcastingRequest`, `VideoEndCallRequestPayload` and `VideoKickUserRequestPayload` are removed, as is `Time`; `EnrichedReaction.created_at` and `updated_at` are `str`
34
+ * `AIVideoConfig.provider` is removed
35
+
36
+ ### Features
37
+
38
+ * add `fork_channel`, `fork_message_ids` and `rewind_channel`, also on `Channel` as `fork`, `fork_message_ids` and `rewind` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
39
+ * add `query_channel_bans`, `search_all`, `claim_guest_user`, `check_gcp_pub_sub` and the history window team methods `get_history_window_teams`, `set_history_window_teams` and `delete_history_window_teams` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
40
+ * add `query_deleted_channels` to list deleted channels ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
41
+ * add scheduled messages: `send_message(scheduled_at=...)`, `query_scheduled_messages`, `update_scheduled_message`, `delete_scheduled_message` and `send_scheduled_message_now` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
42
+ * **feeds:** add feed exports (`export_feeds_app`, `export_feeds_by_type`, `cancel_export_feeds`), retention policies (`feeds_get_retention_policy`, `feeds_set_retention_policy`, `feeds_delete_retention_policy`, `feeds_get_retention_policy_runs`), user interests (`upsert_user_interests`, `delete_user_interests`), `comment_feedback` and `get_notification_status` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
43
+ * **moderation:** add config versions (`save_config_version`, `list_config_versions`, `get_config_version`, `publish_config_version`), `query_moderation_feedback` and `reveal_media`, plus `matched_blocklists` on `ModerationRulesTriggeredEvent` and `severities` on `ClosedCaptionRuleParameters` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
44
+ * page through `list_imports` with `limit` and `offset`, and set `ImportV2TaskSettings.custom_format` to read custom data from the root of each item or from a nested `custom` object ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
45
+ * regenerate the client from the current Stream API ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
46
+ * **video:** add `query_call_ring_attempts` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
16
47
 
17
48
 
18
49
  ### Bug Fixes
19
50
 
20
- * cap decompressed gzip webhook bodies at 10 MiB (`getstream.webhook.MAX_WEBHOOK_PAYLOAD_BYTES`) and reject data after the gzip stream, because decompression runs before the signature check ([138e50a](https://github.com/GetStream/stream-py/commit/138e50ad2a713a85eef61508d6cf854d64692460))
21
51
  * stop double-encoding list query parameters ([#306](https://github.com/GetStream/stream-py/issues/306)) ([a584118](https://github.com/GetStream/stream-py/commit/a584118a15db9bd60b7daf571fa13d52e459b6d2)), closes [#305](https://github.com/GetStream/stream-py/issues/305)
22
52
 
23
53
  ## [7.1.0](https://github.com/GetStream/stream-py/compare/v7.0.0...v7.1.0) (2026-11-04)
@@ -0,0 +1,114 @@
1
+ # Migration Guide: v7 → v8
2
+
3
+ v8 regenerates the client from the current Stream API. Most of the changes add new methods, models and optional parameters. This guide lists the changes that can break code written for stream-py 7.1.0.
4
+
5
+ ```bash
6
+ pip install "getstream==8.*"
7
+ ```
8
+
9
+ ## Pass optional parameters by keyword
10
+
11
+ Generated method parameters are positional-or-keyword, and v8 adds new optional parameters in the middle of many signatures. A call that passes optional arguments by position can now send a value to the wrong parameter. Calls that pass them by keyword work unchanged.
12
+
13
+ ```python
14
+ # v7: the fourth positional argument was `user_id`
15
+ client.feeds.get_activity(activity_id, "first", 10, "jane")
16
+
17
+ # v8: "jane" would go to the new `skip_own_followings`, so pass it by keyword
18
+ client.feeds.get_activity(activity_id, comment_sort="first", comment_limit=10, user_id="jane")
19
+ ```
20
+
21
+ These methods have new parameters before existing ones. Each async client has the same changes.
22
+
23
+ | Method | New parameters | First moved parameter |
24
+ |---|---|---|
25
+ | `Channel.get_or_create`, `chat.get_or_create_channel`, `chat.get_or_create_distinct_channel` | `include_scheduled_messages` | `state` |
26
+ | `Channel.send_message`, `chat.send_message` | `scheduled_at` | `skip_enrich_url` |
27
+ | `chat.query_channels` | `include_active_member_count` | `limit` |
28
+ | `chat.grouped_query_channels` | `sort` | `groups` |
29
+ | `chat.channel_batch_update` | `synchronous` | `custom_unset` |
30
+ | `chat.create_channel_type` | `quotes`, `reminders`, `automod_thresholds` | `reactions` |
31
+ | `chat.create_reminder`, `chat.update_reminder` | `expires_at` | `remind_at` |
32
+ | `chat.cast_poll_vote`, `feeds.cast_poll_vote` | `config_key`, `config_team` | `user_id` |
33
+ | `create_poll` | `config_key`, `config_team`, `team` | `description` |
34
+ | `update_poll` | `config_key`, `config_team` | `description` |
35
+ | `update_poll_partial`, `create_poll_option`, `update_poll_option` | `config_key`, `config_team` | `user_id` |
36
+ | `update_app` | `two_way_block_effect_enabled` | `user_response_time_enabled` |
37
+ | `Feed.get_or_create`, `feeds.get_or_create_feed` | `discard_actors` | `data` |
38
+ | `Feed.query_pinned_activities`, `feeds.query_pinned_activities` | `include_expired_activities`, `include_moderated_activities`, `include_soft_deleted_activities`, `user_id` | `limit` |
39
+ | `feeds.add_activity` | `skip_existing` | `skip_push` |
40
+ | `feeds.get_activity` | `skip_own_followings` | `user_id` |
41
+ | `feeds.query_activities` | `include_moderated_activities` | `include_private_activities` |
42
+ | `feeds.query_comments` | `include_soft_deleted_comments` | `limit` |
43
+ | `feeds.query_follows` | `include_group_counts` | `limit` |
44
+ | `feeds.follow_batch`, `feeds.get_or_create_follows` | `_async` | `create_users` |
45
+ | `feeds.unfollow_batch`, `feeds.get_or_create_unfollows` | `_async` | `delete_notification_activity` |
46
+ | `moderation.analyze` | `country_code`, `country_codes` | `entity_creator_id` |
47
+ | `moderation.check` | `country_code`, `country_codes` | `test_mode` |
48
+ | `moderation.appeal` | `channel_cid` | `review_queue_item_id` |
49
+ | `moderation.ban` | `ban_from_future_channels`, `delete_reactions` | `banned_by_id` |
50
+ | `moderation.unban` | `remove_future_channels_ban` | `unbanned_by_id` |
51
+ | `moderation.submit_action` | `delete_poll`, `delete_poll_answer`, `delete_poll_option` | `delete_reaction` |
52
+ | `moderation.upsert_config` | `engine`, `intent_config` | `flood_config` |
53
+
54
+ ## Parameters that are now optional
55
+
56
+ These parameters are now optional, so they moved behind the required ones. Pass them by keyword.
57
+
58
+ - `chat.update_channel_type`: `automod`, `automod_behavior` and `max_message_length`. `max_message_length` now comes after `mark_messages_pending`.
59
+ - `moderation.submit_moderation_feedback`: `message`, `published_at` and `reference`. They now come after `channel_id` and the new `confidence`, `entity_creator_id`, `entity_id`, `entity_type`, `false_positive` and `feedback_type` parameters, and before the new `review_queue_item_id` and `user_id`.
60
+
61
+ The matching request models, `UpdateChannelTypeRequest` and `SubmitModerationFeedbackRequest`, give these fields a `None` default, so they also moved in the dataclass field order.
62
+
63
+ ## Campaign responses
64
+
65
+ `update_campaign` returns `UpdateCampaignResponse` and `stop_campaign` returns `StopCampaignResponse`. v7 decoded both into `CampaignResponse`, whose fields came back as `None` because the API returns the campaign inside a `campaign` field. Read the campaign from that field.
66
+
67
+ ```python
68
+ # v7
69
+ resp = client.chat.stop_campaign(id=campaign_id)
70
+ status = resp.data.status # None
71
+
72
+ # v8
73
+ resp = client.chat.stop_campaign(id=campaign_id)
74
+ status = resp.data.campaign.status
75
+ ```
76
+
77
+ ## Removed models
78
+
79
+ These request models had no fields, because their endpoints take no request body. The methods that call those endpoints keep their signatures.
80
+
81
+ - `CancelImportV2TaskRequest`
82
+ - `CommitMessageRequest`
83
+ - `EndCallRequest`
84
+ - `ExportFeedUserDataRequest`
85
+ - `RestoreFeedGroupRequest`
86
+ - `StartHLSBroadcastingRequest`
87
+ - `StartPolicyTestRunRequest`
88
+ - `StopAllRTMPBroadcastsRequest`
89
+ - `StopCampaignRequest`
90
+ - `StopFrameRecordingRequest`
91
+ - `StopHLSBroadcastingRequest`
92
+ - `VideoEndCallRequestPayload`
93
+ - `VideoKickUserRequestPayload`
94
+
95
+ `Time` was also removed. `EnrichedReaction.created_at` and `EnrichedReaction.updated_at` are now `Optional[str]`.
96
+
97
+ ## Removed field
98
+
99
+ `AIVideoConfig.provider` was removed. Drop it from `AIVideoConfig(...)` calls in your moderation config.
100
+
101
+ ## Type annotation changes
102
+
103
+ These changes affect only type checkers. Runtime values are unchanged.
104
+
105
+ - `own_capabilities` on call, channel and feed responses is annotated as `List[str]`. The constants classes `OwnCapability`, `ChannelOwnCapability` and `FeedOwnCapability` still exist, and their values are strings.
106
+ - `CampaignResponse.sender_mode` and `CampaignResponse.sender_visibility` are now `Optional[str]`.
107
+
108
+ ## Required response fields
109
+
110
+ `ChannelMemberResponse.user_id` and `OwnUserResponse.blocked_user_ids` are now required and have no default. Responses the SDK decodes are unaffected, but code that builds these models itself, such as test fixtures, must pass both fields, or the constructor raises `TypeError` and a direct `from_dict` without `infer_missing=True` raises `KeyError`.
111
+
112
+ ## Uploads
113
+
114
+ `upload_file`, `upload_image`, `upload_channel_file` and `upload_channel_image` keep their signatures and still send `multipart/form-data`. `file` is a keyword with a `None` default in the signature, but the upload raises `ValueError` without it.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: getstream
3
- Version: 7.2.0
3
+ Version: 8.1.0
4
4
  Summary: GetStream Python SDK - Build scalable activity feeds, chat, and video calling applications
5
5
  Author-email: sachaarbonel <sacha.arbonel@hotmail.fr>, tbarbugli <tbarbugli@gmail.com>
6
6
  License-File: LICENSE.md
@@ -7,7 +7,7 @@ import time
7
7
  import uuid
8
8
  import warnings
9
9
  import asyncio
10
- from typing import Any, Dict, List, Optional, Tuple, Type, cast, get_origin
10
+ from typing import Any, Dict, Optional, Tuple, Type, cast, get_origin
11
11
 
12
12
  from getstream.exceptions import (
13
13
  StreamApiException,
@@ -143,6 +143,20 @@ def _read_file_bytes(file_path: str) -> bytes:
143
143
  return f.read()
144
144
 
145
145
 
146
+ def _multipart_form(body: Dict[str, Any]) -> Tuple[str, Dict[str, str]]:
147
+ """Split an upload body into the file path and the other set fields as form
148
+ values: strings as-is, anything else JSON-encoded, which is how the API reads
149
+ "user" and "upload_sizes"."""
150
+ fields = _strip_none(body)
151
+ file_path = fields.pop("file", None)
152
+ if not file_path:
153
+ raise ValueError("file path must be provided")
154
+ return file_path, {
155
+ name: value if isinstance(value, str) else json.dumps(value)
156
+ for name, value in fields.items()
157
+ }
158
+
159
+
146
160
  def _strip_none(obj):
147
161
  """Recursively remove None values from dicts so unset optional fields
148
162
  are omitted from the JSON body instead of being sent as null. ``NULL``
@@ -569,23 +583,19 @@ class BaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, ABC):
569
583
  self,
570
584
  path: str,
571
585
  data_type: Type[T],
572
- file_path: str,
573
586
  *,
574
587
  path_params: Optional[Dict[str, str]] = None,
575
588
  query_params: Optional[Dict[str, str]] = None,
576
- form_fields: Optional[List[Tuple[str, str]]] = None,
589
+ json: Dict[str, Any],
577
590
  ) -> StreamResponse[T]:
578
591
  """Send a multipart/form-data upload request, matching Go/PHP SDK behavior."""
592
+ file_path, data = _multipart_form(json)
579
593
  file_name = os.path.basename(file_path)
580
594
  content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
581
595
  with open(file_path, "rb") as f:
582
596
  file_content = f.read()
583
597
 
584
598
  files = {"file": (file_name, file_content, content_type)}
585
- data: Dict[str, str] = {}
586
- for field_name, field_value in form_fields or []:
587
- data[field_name] = field_value
588
-
589
599
  kwargs: Dict[str, Any] = {"files": files}
590
600
  if data:
591
601
  kwargs["data"] = data
@@ -700,23 +710,19 @@ class AsyncBaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, A
700
710
  self,
701
711
  path: str,
702
712
  data_type: Type[T],
703
- file_path: str,
704
713
  *,
705
714
  path_params: Optional[Dict[str, str]] = None,
706
715
  query_params: Optional[Dict[str, str]] = None,
707
- form_fields: Optional[List[Tuple[str, str]]] = None,
716
+ json: Dict[str, Any],
708
717
  ) -> StreamResponse[T]:
709
718
  """Send a multipart/form-data upload request, matching Go/PHP SDK behavior."""
719
+ file_path, data = _multipart_form(json)
710
720
  file_name = os.path.basename(file_path)
711
721
  content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
712
722
 
713
723
  file_content = await asyncio.to_thread(_read_file_bytes, file_path)
714
724
 
715
725
  files = {"file": (file_name, file_content, content_type)}
716
- data: Dict[str, str] = {}
717
- for field_name, field_value in form_fields or []:
718
- data[field_name] = field_value
719
-
720
726
  kwargs: Dict[str, Any] = {"files": files}
721
727
  if data:
722
728
  kwargs["data"] = data
@@ -18,7 +18,12 @@ class Channel:
18
18
  self.custom_data = custom_data or {}
19
19
 
20
20
  def _sync_from_response(self, data):
21
- if hasattr(data, "channel") and isinstance(data.channel, ChannelResponse):
21
+ # A fork's response carries the new channel, not this one.
22
+ if (
23
+ hasattr(data, "channel")
24
+ and isinstance(data.channel, ChannelResponse)
25
+ and data.channel.cid == f"{self.channel_type}:{self.channel_id}"
26
+ ):
22
27
  self.custom_data = data.channel.custom
23
28
 
24
29
  @attach_channel_cid_async
@@ -131,6 +136,33 @@ class Channel:
131
136
  self._sync_from_response(response.data)
132
137
  return response
133
138
 
139
+ @attach_channel_cid_async
140
+ async def query_channel_bans(
141
+ self,
142
+ exclude_expired_bans: Optional[bool] = None,
143
+ include_total: Optional[bool] = None,
144
+ limit: Optional[int] = None,
145
+ next: Optional[str] = None,
146
+ prev: Optional[str] = None,
147
+ user_id: Optional[str] = None,
148
+ filter_conditions: Optional[Dict[str, object]] = None,
149
+ user: Optional[UserRequest] = None,
150
+ ) -> StreamResponse[QueryChannelBansResponse]:
151
+ response = await self.client.query_channel_bans(
152
+ type=self.channel_type,
153
+ id=self.channel_id,
154
+ exclude_expired_bans=exclude_expired_bans,
155
+ include_total=include_total,
156
+ limit=limit,
157
+ next=next,
158
+ prev=prev,
159
+ user_id=user_id,
160
+ filter_conditions=filter_conditions,
161
+ user=user,
162
+ )
163
+ self._sync_from_response(response.data)
164
+ return response
165
+
134
166
  @attach_channel_cid_async
135
167
  async def delete_draft(
136
168
  self, parent_id: Optional[str] = None, user_id: Optional[str] = None
@@ -185,6 +217,44 @@ class Channel:
185
217
  self._sync_from_response(response.data)
186
218
  return response
187
219
 
220
+ @attach_channel_cid_async
221
+ async def fork(
222
+ self,
223
+ message_id: str,
224
+ channel: ForkChannelTarget,
225
+ exclude_attachments: Optional[bool] = None,
226
+ user_id: Optional[str] = None,
227
+ user: Optional[UserRequest] = None,
228
+ ) -> StreamResponse[ForkChannelResponse]:
229
+ response = await self.client.fork_channel(
230
+ type=self.channel_type,
231
+ id=self.channel_id,
232
+ message_id=message_id,
233
+ channel=channel,
234
+ exclude_attachments=exclude_attachments,
235
+ user_id=user_id,
236
+ user=user,
237
+ )
238
+ self._sync_from_response(response.data)
239
+ return response
240
+
241
+ @attach_channel_cid_async
242
+ async def fork_message_ids(
243
+ self,
244
+ source_message_ids: List[str],
245
+ user_id: Optional[str] = None,
246
+ user: Optional[UserRequest] = None,
247
+ ) -> StreamResponse[ForkMessageIDsResponse]:
248
+ response = await self.client.fork_message_ids(
249
+ type=self.channel_type,
250
+ id=self.channel_id,
251
+ source_message_ids=source_message_ids,
252
+ user_id=user_id,
253
+ user=user,
254
+ )
255
+ self._sync_from_response(response.data)
256
+ return response
257
+
188
258
  @attach_channel_cid_async
189
259
  async def hide(
190
260
  self,
@@ -255,6 +325,7 @@ class Channel:
255
325
  include_mentioned_members: Optional[bool] = None,
256
326
  keep_channel_hidden: Optional[bool] = None,
257
327
  pending: Optional[bool] = None,
328
+ scheduled_at: Optional[datetime] = None,
258
329
  skip_enrich_url: Optional[bool] = None,
259
330
  skip_push: Optional[bool] = None,
260
331
  pending_message_metadata: Optional[Dict[str, str]] = None,
@@ -268,6 +339,7 @@ class Channel:
268
339
  include_mentioned_members=include_mentioned_members,
269
340
  keep_channel_hidden=keep_channel_hidden,
270
341
  pending=pending,
342
+ scheduled_at=scheduled_at,
271
343
  skip_enrich_url=skip_enrich_url,
272
344
  skip_push=skip_push,
273
345
  pending_message_metadata=pending_message_metadata,
@@ -333,6 +405,7 @@ class Channel:
333
405
  async def get_or_create(
334
406
  self,
335
407
  hide_for_creator: Optional[bool] = None,
408
+ include_scheduled_messages: Optional[bool] = None,
336
409
  state: Optional[bool] = None,
337
410
  thread_unread_counts: Optional[bool] = None,
338
411
  member_custom_include: Optional[List[str]] = None,
@@ -345,6 +418,7 @@ class Channel:
345
418
  type=self.channel_type,
346
419
  id=self.channel_id,
347
420
  hide_for_creator=hide_for_creator,
421
+ include_scheduled_messages=include_scheduled_messages,
348
422
  state=state,
349
423
  thread_unread_counts=thread_unread_counts,
350
424
  member_custom_include=member_custom_include,
@@ -375,6 +449,27 @@ class Channel:
375
449
  self._sync_from_response(response.data)
376
450
  return response
377
451
 
452
+ @attach_channel_cid_async
453
+ async def rewind(
454
+ self,
455
+ expected_last_message_id: str,
456
+ message_id: str,
457
+ inclusive: Optional[bool] = None,
458
+ user_id: Optional[str] = None,
459
+ user: Optional[UserRequest] = None,
460
+ ) -> StreamResponse[RewindChannelResponse]:
461
+ response = await self.client.rewind_channel(
462
+ type=self.channel_type,
463
+ id=self.channel_id,
464
+ expected_last_message_id=expected_last_message_id,
465
+ message_id=message_id,
466
+ inclusive=inclusive,
467
+ user_id=user_id,
468
+ user=user,
469
+ )
470
+ self._sync_from_response(response.data)
471
+ return response
472
+
378
473
  @attach_channel_cid_async
379
474
  async def show(
380
475
  self, user_id: Optional[str] = None, user: Optional[UserRequest] = None
@@ -1,13 +1,10 @@
1
- import json
2
1
  from typing import List, Optional
3
2
 
4
3
  from getstream.chat.async_channel import Channel
5
4
  from getstream.chat.async_rest_client import ChatRestClient
6
- from getstream.common import telemetry
7
5
  from getstream.models import (
8
6
  ImageSize,
9
7
  OnlyUserID,
10
- UploadChannelFileResponse,
11
8
  UploadChannelResponse,
12
9
  )
13
10
  from getstream.stream_response import StreamResponse
@@ -27,26 +24,7 @@ class ChatClient(ChatRestClient):
27
24
  def channel(self, call_type: str, id: str) -> Channel:
28
25
  return Channel(self, call_type, id)
29
26
 
30
- @telemetry.operation_name("getstream.api.chat.upload_channel_file")
31
- async def upload_channel_file(
32
- self,
33
- type: str,
34
- id: str,
35
- file: str,
36
- user: Optional[OnlyUserID] = None,
37
- ) -> StreamResponse[UploadChannelFileResponse]:
38
- form_fields = []
39
- if user is not None:
40
- form_fields.append(("user", json.dumps(user.to_dict())))
41
- return await self._upload_multipart(
42
- "/api/v2/chat/channels/{type}/{id}/file",
43
- UploadChannelFileResponse,
44
- file,
45
- path_params={"type": type, "id": id},
46
- form_fields=form_fields,
47
- )
48
-
49
- @telemetry.operation_name("getstream.api.chat.upload_channel_image")
27
+ # Keeps the `channel_type` name this method has always taken; the generated one takes `type`.
50
28
  async def upload_channel_image(
51
29
  self,
52
30
  channel_type: Optional[str] = None,
@@ -56,7 +34,6 @@ class ChatClient(ChatRestClient):
56
34
  user: Optional[OnlyUserID] = None,
57
35
  **kwargs,
58
36
  ) -> StreamResponse[UploadChannelResponse]:
59
- # Backward compatibility for generated wrappers passing `type=...`.
60
37
  if channel_type is None:
61
38
  channel_type = kwargs.pop("type", None)
62
39
  if kwargs:
@@ -69,17 +46,6 @@ class ChatClient(ChatRestClient):
69
46
  raise TypeError("upload_channel_image() missing required argument: 'id'")
70
47
  if file is None:
71
48
  raise TypeError("upload_channel_image() missing required argument: 'file'")
72
- form_fields = []
73
- if user is not None:
74
- form_fields.append(("user", json.dumps(user.to_dict())))
75
- if upload_sizes is not None:
76
- form_fields.append(
77
- ("upload_sizes", json.dumps([s.to_dict() for s in upload_sizes]))
78
- )
79
- return await self._upload_multipart(
80
- "/api/v2/chat/channels/{type}/{id}/image",
81
- UploadChannelResponse,
82
- file,
83
- path_params={"type": channel_type, "id": id},
84
- form_fields=form_fields,
49
+ return await super().upload_channel_image(
50
+ channel_type, id, file=file, upload_sizes=upload_sizes, user=user
85
51
  )