getstream 7.0.0__tar.gz → 8.0.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.
- {getstream-7.0.0 → getstream-8.0.0}/.release-please-manifest.json +1 -1
- {getstream-7.0.0 → getstream-8.0.0}/CHANGELOG.md +36 -0
- getstream-8.0.0/MIGRATION_v7_to_v8.md +114 -0
- {getstream-7.0.0 → getstream-8.0.0}/PKG-INFO +13 -1
- {getstream-7.0.0 → getstream-8.0.0}/README.md +12 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/README.md +1 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/__init__.py +1 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/base.py +24 -14
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/async_channel.py +96 -1
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/async_client.py +3 -37
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/async_rest_client.py +367 -88
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/channel.py +96 -1
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/client.py +3 -37
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/rest_client.py +365 -88
- getstream-8.0.0/getstream/common/async_client.py +12 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/common/async_rest_client.py +136 -44
- getstream-8.0.0/getstream/common/client.py +12 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/common/rest_client.py +132 -44
- {getstream-7.0.0 → getstream-8.0.0}/getstream/feeds/feeds.py +23 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/feeds/rest_client.py +335 -67
- {getstream-7.0.0 → getstream-8.0.0}/getstream/models/__init__.py +10286 -5495
- {getstream-7.0.0 → getstream-8.0.0}/getstream/moderation/async_rest_client.py +234 -23
- {getstream-7.0.0 → getstream-8.0.0}/getstream/moderation/rest_client.py +236 -23
- {getstream-7.0.0 → getstream-8.0.0}/getstream/tests/test_webhook.py +125 -9
- {getstream-7.0.0 → getstream-8.0.0}/getstream/utils/__init__.py +18 -9
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/async_call.py +9 -2
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/async_rest_client.py +50 -20
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/call.py +9 -2
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rest_client.py +50 -20
- {getstream-7.0.0 → getstream-8.0.0}/getstream/webhook.py +114 -57
- {getstream-7.0.0 → getstream-8.0.0}/pyproject.toml +1 -1
- {getstream-7.0.0 → getstream-8.0.0}/uv.lock +1 -1
- getstream-7.0.0/getstream/common/async_client.py +0 -58
- getstream-7.0.0/getstream/common/client.py +0 -58
- {getstream-7.0.0 → getstream-8.0.0}/.cursor/worktrees.json +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.env.example +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/actions/build-rtc-wheel/action.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/actions/python-uv-setup/action.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/ci.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/pr_title.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/release.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/rtc_wheels.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/run_integration.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/run_tests.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/scheduled_test.yml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.github/workflows/stream-py.code-workspace +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.gitignore +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.gitmodules +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/.pre-commit-config.yaml +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/AGENTS.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/DEVELOPMENT.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/LICENSE.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/MIGRATION_v2_to_v3.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/MIGRATION_v6_to_v7.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/Makefile +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/dev.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/01-setup-and-auth.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/02-users.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/03-channels.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/04-messages-and-reactions.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/05-moderation.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/docs/migration-from-stream-chat-python/06-devices.md +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/generate.sh +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/generate_webrtc.sh +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/chat/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/common/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/common/telemetry.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/config.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/exceptions.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/feeds/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/feeds/client.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/generic.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/logging_utils.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/meta.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/moderation/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/moderation/async_client.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/moderation/client.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/rate_limit.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/stream.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/stream_response.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/tasks.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/utils/event_emitter.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/version.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/async_client.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/client.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/openai.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/audio_forwarder.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/audio_track.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/connection_manager.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/g711.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/participants.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_twirp.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/track_util.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/tracks.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/utils.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/getstream/video/rtc/video_forwarder.py +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/pytest.ini +0 -0
- {getstream-7.0.0 → getstream-8.0.0}/release-please-config.json +0 -0
|
@@ -7,6 +7,42 @@ 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
|
+
## [8.0.0](https://github.com/GetStream/stream-py/compare/v7.1.0...v8.0.0) (2026-11-11)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* 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:
|
|
16
|
+
* 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
|
|
17
|
+
* `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
|
|
18
|
+
* `update_campaign` returns `UpdateCampaignResponse` and `stop_campaign` returns `StopCampaignResponse`; read the campaign from `.campaign`
|
|
19
|
+
* 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`
|
|
20
|
+
* `AIVideoConfig.provider` is removed
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* 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))
|
|
25
|
+
* 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))
|
|
26
|
+
* add `query_deleted_channels` to list deleted channels ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
|
|
27
|
+
* 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))
|
|
28
|
+
* **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))
|
|
29
|
+
* **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))
|
|
30
|
+
* 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))
|
|
31
|
+
* regenerate the client from the current Stream API ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
|
|
32
|
+
* **video:** add `query_call_ring_attempts` ([cc5538e](https://github.com/GetStream/stream-py/commit/cc5538ec188fc7a683c82da7959eaa18358990e7))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
* 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)
|
|
38
|
+
|
|
39
|
+
## [7.1.0](https://github.com/GetStream/stream-py/compare/v7.0.0...v7.1.0) (2026-11-04)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Features
|
|
43
|
+
|
|
44
|
+
* add NULL to send an explicit null in request bodies ([#314](https://github.com/GetStream/stream-py/issues/314)) ([955535e](https://github.com/GetStream/stream-py/commit/955535ed73d2016ccffc7924ed7c0ce60c641284))
|
|
45
|
+
|
|
10
46
|
## [7.0.0](https://github.com/GetStream/stream-py/compare/v6.1.1...v7.0.0) (2026-10-22)
|
|
11
47
|
|
|
12
48
|
|
|
@@ -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:
|
|
3
|
+
Version: 8.0.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
|
|
@@ -186,6 +186,18 @@ client = Stream(api_key=..., api_secret=..., retry=RetryConfig(enabled=True, max
|
|
|
186
186
|
|
|
187
187
|
Only idempotent `GET`/`HEAD` requests are retried, and only on HTTP 429 (unless the backend marked it unrecoverable) or a transport-level failure (timeout, connection reset, DNS, TLS). A 429's `Retry-After` header is honored (clamped to `max_backoff`); otherwise the delay uses full jitter over an exponential backoff. A retried failure logs `http.request.failed` at DEBUG; a final, non-retried failure logs it at ERROR (or not at all for a final 429, since that's already covered by `http.response.received`).
|
|
188
188
|
|
|
189
|
+
### Sending null
|
|
190
|
+
|
|
191
|
+
Request bodies drop `None` values inside objects, so an unset optional field never overwrites stored data. `None` items in a list are kept and sent as `null`. To store an explicit `null`, for example inside custom data, pass `NULL`:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from getstream import NULL
|
|
195
|
+
|
|
196
|
+
channel.update_channel_partial(set={"expires_at": NULL})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
To remove a field instead, use `unset`, for example `unset=["expires_at"]`.
|
|
200
|
+
|
|
189
201
|
### App configuration
|
|
190
202
|
|
|
191
203
|
```python
|
|
@@ -143,6 +143,18 @@ client = Stream(api_key=..., api_secret=..., retry=RetryConfig(enabled=True, max
|
|
|
143
143
|
|
|
144
144
|
Only idempotent `GET`/`HEAD` requests are retried, and only on HTTP 429 (unless the backend marked it unrecoverable) or a transport-level failure (timeout, connection reset, DNS, TLS). A 429's `Retry-After` header is honored (clamped to `max_backoff`); otherwise the delay uses full jitter over an exponential backoff. A retried failure logs `http.request.failed` at DEBUG; a final, non-retried failure logs it at ERROR (or not at all for a final 429, since that's already covered by `http.response.received`).
|
|
145
145
|
|
|
146
|
+
### Sending null
|
|
147
|
+
|
|
148
|
+
Request bodies drop `None` values inside objects, so an unset optional field never overwrites stored data. `None` items in a list are kept and sent as `null`. To store an explicit `null`, for example inside custom data, pass `NULL`:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from getstream import NULL
|
|
152
|
+
|
|
153
|
+
channel.update_channel_partial(set={"expires_at": NULL})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To remove a field instead, use `unset`, for example `unset=["expires_at"]`.
|
|
157
|
+
|
|
146
158
|
### App configuration
|
|
147
159
|
|
|
148
160
|
```python
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
| Custom fields | Top-level dict keys | Nested in `custom` dict |
|
|
21
21
|
| Response access | Dict keys (`response["users"]`) | Typed properties (`response.data.users`) |
|
|
22
22
|
| Env vars | `STREAM_CHAT_TIMEOUT` | `STREAM_API_KEY`, `STREAM_API_SECRET`, `STREAM_TIMEOUT` |
|
|
23
|
+
| `None` in request data | Sent as `null` | Dropped from objects, kept in lists; pass `getstream.NULL` to send `null` |
|
|
23
24
|
|
|
24
25
|
## Quick Example
|
|
25
26
|
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
import uuid
|
|
8
8
|
import warnings
|
|
9
9
|
import asyncio
|
|
10
|
-
from typing import Any, Dict,
|
|
10
|
+
from typing import Any, Dict, Optional, Tuple, Type, cast, get_origin
|
|
11
11
|
|
|
12
12
|
from getstream.exceptions import (
|
|
13
13
|
StreamApiException,
|
|
@@ -22,6 +22,7 @@ from getstream.generic import T
|
|
|
22
22
|
import httpx
|
|
23
23
|
from getstream.config import BaseConfig
|
|
24
24
|
from getstream.version import VERSION
|
|
25
|
+
from getstream.utils import NULL
|
|
25
26
|
from urllib.parse import quote
|
|
26
27
|
from abc import ABC
|
|
27
28
|
from getstream.common.telemetry import (
|
|
@@ -142,9 +143,26 @@ def _read_file_bytes(file_path: str) -> bytes:
|
|
|
142
143
|
return f.read()
|
|
143
144
|
|
|
144
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
|
+
|
|
145
160
|
def _strip_none(obj):
|
|
146
161
|
"""Recursively remove None values from dicts so unset optional fields
|
|
147
|
-
are omitted from the JSON body instead of being sent as null.
|
|
162
|
+
are omitted from the JSON body instead of being sent as null. ``NULL``
|
|
163
|
+
becomes null."""
|
|
164
|
+
if obj is NULL:
|
|
165
|
+
return None
|
|
148
166
|
if isinstance(obj, dict):
|
|
149
167
|
return {k: _strip_none(v) for k, v in obj.items() if v is not None}
|
|
150
168
|
if isinstance(obj, list):
|
|
@@ -565,23 +583,19 @@ class BaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, ABC):
|
|
|
565
583
|
self,
|
|
566
584
|
path: str,
|
|
567
585
|
data_type: Type[T],
|
|
568
|
-
file_path: str,
|
|
569
586
|
*,
|
|
570
587
|
path_params: Optional[Dict[str, str]] = None,
|
|
571
588
|
query_params: Optional[Dict[str, str]] = None,
|
|
572
|
-
|
|
589
|
+
json: Dict[str, Any],
|
|
573
590
|
) -> StreamResponse[T]:
|
|
574
591
|
"""Send a multipart/form-data upload request, matching Go/PHP SDK behavior."""
|
|
592
|
+
file_path, data = _multipart_form(json)
|
|
575
593
|
file_name = os.path.basename(file_path)
|
|
576
594
|
content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
|
577
595
|
with open(file_path, "rb") as f:
|
|
578
596
|
file_content = f.read()
|
|
579
597
|
|
|
580
598
|
files = {"file": (file_name, file_content, content_type)}
|
|
581
|
-
data: Dict[str, str] = {}
|
|
582
|
-
for field_name, field_value in form_fields or []:
|
|
583
|
-
data[field_name] = field_value
|
|
584
|
-
|
|
585
599
|
kwargs: Dict[str, Any] = {"files": files}
|
|
586
600
|
if data:
|
|
587
601
|
kwargs["data"] = data
|
|
@@ -696,23 +710,19 @@ class AsyncBaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, A
|
|
|
696
710
|
self,
|
|
697
711
|
path: str,
|
|
698
712
|
data_type: Type[T],
|
|
699
|
-
file_path: str,
|
|
700
713
|
*,
|
|
701
714
|
path_params: Optional[Dict[str, str]] = None,
|
|
702
715
|
query_params: Optional[Dict[str, str]] = None,
|
|
703
|
-
|
|
716
|
+
json: Dict[str, Any],
|
|
704
717
|
) -> StreamResponse[T]:
|
|
705
718
|
"""Send a multipart/form-data upload request, matching Go/PHP SDK behavior."""
|
|
719
|
+
file_path, data = _multipart_form(json)
|
|
706
720
|
file_name = os.path.basename(file_path)
|
|
707
721
|
content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
|
708
722
|
|
|
709
723
|
file_content = await asyncio.to_thread(_read_file_bytes, file_path)
|
|
710
724
|
|
|
711
725
|
files = {"file": (file_name, file_content, content_type)}
|
|
712
|
-
data: Dict[str, str] = {}
|
|
713
|
-
for field_name, field_value in form_fields or []:
|
|
714
|
-
data[field_name] = field_value
|
|
715
|
-
|
|
716
726
|
kwargs: Dict[str, Any] = {"files": files}
|
|
717
727
|
if data:
|
|
718
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
)
|