getstream 7.0.0__tar.gz → 7.2.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-7.2.0}/.release-please-manifest.json +1 -1
- {getstream-7.0.0 → getstream-7.2.0}/CHANGELOG.md +20 -0
- {getstream-7.0.0 → getstream-7.2.0}/PKG-INFO +13 -1
- {getstream-7.0.0 → getstream-7.2.0}/README.md +12 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/README.md +1 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/__init__.py +1 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/base.py +5 -1
- {getstream-7.0.0 → getstream-7.2.0}/getstream/models/__init__.py +12 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/tests/test_webhook.py +12 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/utils/__init__.py +18 -9
- {getstream-7.0.0 → getstream-7.2.0}/getstream/webhook.py +24 -10
- {getstream-7.0.0 → getstream-7.2.0}/pyproject.toml +1 -1
- {getstream-7.0.0 → getstream-7.2.0}/uv.lock +1 -1
- {getstream-7.0.0 → getstream-7.2.0}/.cursor/worktrees.json +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.env.example +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/actions/build-rtc-wheel/action.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/actions/python-uv-setup/action.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/ci.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/pr_title.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/release.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/rtc_wheels.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/run_integration.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/run_tests.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/scheduled_test.yml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.github/workflows/stream-py.code-workspace +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.gitignore +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.gitmodules +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/.pre-commit-config.yaml +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/AGENTS.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/DEVELOPMENT.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/LICENSE.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/MIGRATION_v2_to_v3.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/MIGRATION_v6_to_v7.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/Makefile +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/dev.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/01-setup-and-auth.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/02-users.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/03-channels.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/04-messages-and-reactions.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/05-moderation.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/06-devices.md +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/generate.sh +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/generate_webrtc.sh +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/async_channel.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/async_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/async_rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/channel.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/chat/rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/async_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/async_rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/common/telemetry.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/config.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/exceptions.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/feeds/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/feeds/client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/feeds/feeds.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/feeds/rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/generic.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/logging_utils.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/meta.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/moderation/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/moderation/async_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/moderation/async_rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/moderation/client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/moderation/rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/rate_limit.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/stream.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/stream_response.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/tasks.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/utils/event_emitter.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/version.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/async_call.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/async_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/async_rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/call.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/openai.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rest_client.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/audio_forwarder.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/audio_track.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/connection_manager.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/g711.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/participants.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.pyi +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_twirp.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/track_util.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/tracks.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/utils.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/video_forwarder.py +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/pytest.ini +0 -0
- {getstream-7.0.0 → getstream-7.2.0}/release-please-config.json +0 -0
|
@@ -7,6 +7,26 @@ 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)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
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))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
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
|
+
* 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
|
+
|
|
23
|
+
## [7.1.0](https://github.com/GetStream/stream-py/compare/v7.0.0...v7.1.0) (2026-11-04)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* 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))
|
|
29
|
+
|
|
10
30
|
## [7.0.0](https://github.com/GetStream/stream-py/compare/v6.1.1...v7.0.0) (2026-10-22)
|
|
11
31
|
|
|
12
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: getstream
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.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
|
|
|
@@ -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 (
|
|
@@ -144,7 +145,10 @@ def _read_file_bytes(file_path: str) -> bytes:
|
|
|
144
145
|
|
|
145
146
|
def _strip_none(obj):
|
|
146
147
|
"""Recursively remove None values from dicts so unset optional fields
|
|
147
|
-
are omitted from the JSON body instead of being sent as null.
|
|
148
|
+
are omitted from the JSON body instead of being sent as null. ``NULL``
|
|
149
|
+
becomes null."""
|
|
150
|
+
if obj is NULL:
|
|
151
|
+
return None
|
|
148
152
|
if isinstance(obj, dict):
|
|
149
153
|
return {k: _strip_none(v) for k, v in obj.items() if v is not None}
|
|
150
154
|
if isinstance(obj, list):
|
|
@@ -3927,6 +3927,9 @@ class CallCreatedEvent(DataClassJsonMixin):
|
|
|
3927
3927
|
|
|
3928
3928
|
@dataclass
|
|
3929
3929
|
class CallCustomPropertyParameters(DataClassJsonMixin):
|
|
3930
|
+
expected_value: object | None = dc_field(
|
|
3931
|
+
default=None, metadata=dc_config(field_name="expected_value")
|
|
3932
|
+
)
|
|
3930
3933
|
operator: str | None = dc_field(
|
|
3931
3934
|
default=None, metadata=dc_config(field_name="operator")
|
|
3932
3935
|
)
|
|
@@ -9268,6 +9271,9 @@ class ContentCountRuleParameters(DataClassJsonMixin):
|
|
|
9268
9271
|
|
|
9269
9272
|
@dataclass
|
|
9270
9273
|
class ContentCustomPropertyCountParameters(DataClassJsonMixin):
|
|
9274
|
+
expected_value: object | None = dc_field(
|
|
9275
|
+
default=None, metadata=dc_config(field_name="expected_value")
|
|
9276
|
+
)
|
|
9271
9277
|
operator: str | None = dc_field(
|
|
9272
9278
|
default=None, metadata=dc_config(field_name="operator")
|
|
9273
9279
|
)
|
|
@@ -9284,6 +9290,9 @@ class ContentCustomPropertyCountParameters(DataClassJsonMixin):
|
|
|
9284
9290
|
|
|
9285
9291
|
@dataclass
|
|
9286
9292
|
class ContentCustomPropertyParameters(DataClassJsonMixin):
|
|
9293
|
+
expected_value: object | None = dc_field(
|
|
9294
|
+
default=None, metadata=dc_config(field_name="expected_value")
|
|
9295
|
+
)
|
|
9287
9296
|
operator: str | None = dc_field(
|
|
9288
9297
|
default=None, metadata=dc_config(field_name="operator")
|
|
9289
9298
|
)
|
|
@@ -30303,6 +30312,9 @@ class UserCustomEventRequest(DataClassJsonMixin):
|
|
|
30303
30312
|
|
|
30304
30313
|
@dataclass
|
|
30305
30314
|
class UserCustomPropertyParameters(DataClassJsonMixin):
|
|
30315
|
+
expected_value: object | None = dc_field(
|
|
30316
|
+
default=None, metadata=dc_config(field_name="expected_value")
|
|
30317
|
+
)
|
|
30306
30318
|
operator: str | None = dc_field(
|
|
30307
30319
|
default=None, metadata=dc_config(field_name="operator")
|
|
30308
30320
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT.
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
|
+
import gzip
|
|
4
5
|
import hmac
|
|
5
6
|
import warnings
|
|
6
7
|
from pathlib import Path
|
|
@@ -11,6 +12,7 @@ from getstream.webhook import (
|
|
|
11
12
|
InvalidWebhookError,
|
|
12
13
|
UnknownEvent,
|
|
13
14
|
get_event_type,
|
|
15
|
+
gunzip_payload,
|
|
14
16
|
parse_event,
|
|
15
17
|
parse_sns,
|
|
16
18
|
parse_sqs,
|
|
@@ -1261,6 +1263,16 @@ class TestWebhookConformanceNegative:
|
|
|
1261
1263
|
with pytest.raises(InvalidWebhookError, match=InvalidWebhookError.GZIP_FAILED):
|
|
1262
1264
|
verify_and_parse_webhook(body, sig, CANONICAL_TEST_SECRET)
|
|
1263
1265
|
|
|
1266
|
+
def test_oversized_compression(self):
|
|
1267
|
+
body = _read_bytes(self._neg("oversized_compression") / "body.gz")
|
|
1268
|
+
sig = _read_str(self._neg("oversized_compression") / "signature.txt")
|
|
1269
|
+
with pytest.raises(InvalidWebhookError, match="payload exceeds"):
|
|
1270
|
+
verify_and_parse_webhook(body, sig, CANONICAL_TEST_SECRET)
|
|
1271
|
+
|
|
1272
|
+
def test_trailing_data_after_gzip(self):
|
|
1273
|
+
with pytest.raises(InvalidWebhookError, match="trailing data"):
|
|
1274
|
+
gunzip_payload(gzip.compress(b"{}") + b"XYZ")
|
|
1275
|
+
|
|
1264
1276
|
def test_bad_base64(self):
|
|
1265
1277
|
# Per CHA-3071 wire format: decode_sqs_payload falls back to raw bytes when
|
|
1266
1278
|
# base64 decoding fails (uncompressed wire format). For input that is
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import enum
|
|
1
2
|
import json
|
|
2
3
|
import asyncio
|
|
3
4
|
from typing import Dict, List, Optional, Union, cast
|
|
4
|
-
from urllib.parse import quote
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from datetime import timezone
|
|
7
7
|
from urllib.parse import urlparse, urlunparse
|
|
@@ -36,18 +36,27 @@ def validate_and_clean_url(url):
|
|
|
36
36
|
return cleaned_url
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# An Enum member because dataclasses-json deep-copies dict values, and a deep copy of an Enum member is the member itself. Its value None makes JSON encoders write null.
|
|
40
|
+
class _Null(enum.Enum):
|
|
41
|
+
NULL = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
NULL = _Null.NULL
|
|
45
|
+
"""Sends an explicit JSON ``null``. Request bodies drop ``None`` values inside objects, so use ``NULL`` where ``null`` must reach the API, for example inside custom data."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def encode_datetime(date: Union[datetime, _Null, None]) -> Union[str, _Null, None]:
|
|
40
49
|
"""
|
|
41
50
|
Encodes a datetime object into an ISO 8601 formatted string.
|
|
42
51
|
|
|
43
52
|
Args:
|
|
44
|
-
date (
|
|
53
|
+
date (Union[datetime, _Null, None]): The datetime object to encode.
|
|
45
54
|
|
|
46
55
|
Returns:
|
|
47
|
-
|
|
56
|
+
Union[str, _Null, None]: The ISO 8601 string representation of the datetime, or the input unchanged if it is None or NULL.
|
|
48
57
|
"""
|
|
49
|
-
if date
|
|
50
|
-
return
|
|
58
|
+
if not isinstance(date, datetime):
|
|
59
|
+
return date
|
|
51
60
|
return date.isoformat()
|
|
52
61
|
|
|
53
62
|
|
|
@@ -110,7 +119,7 @@ def build_query_param(**kwargs):
|
|
|
110
119
|
- JSON-serializable objects with a `to_json` method will be serialized using that method.
|
|
111
120
|
- datetime objects are converted to ISO 8601 strings.
|
|
112
121
|
- Booleans are converted to lowercase strings.
|
|
113
|
-
- Lists are converted to comma-separated strings
|
|
122
|
+
- Lists are converted to comma-separated strings.
|
|
114
123
|
- Other types (strings, integers, dictionaries) are handled appropriately.
|
|
115
124
|
|
|
116
125
|
Args:
|
|
@@ -132,7 +141,7 @@ def build_query_param(**kwargs):
|
|
|
132
141
|
elif isinstance(value, (str, int)):
|
|
133
142
|
params[key] = str(value)
|
|
134
143
|
elif isinstance(value, list):
|
|
135
|
-
params[key] = ",".join(
|
|
144
|
+
params[key] = ",".join(_serialize_query_value(v) for v in value)
|
|
136
145
|
else:
|
|
137
146
|
# For dictionaries or any other types of objects
|
|
138
147
|
params[key] = json.dumps(value)
|
|
@@ -148,7 +157,7 @@ async def build_query_param_async(**kwargs):
|
|
|
148
157
|
This function handles various data types:
|
|
149
158
|
- JSON-serializable objects with a `to_json` method will be serialized using that method.
|
|
150
159
|
- Booleans are converted to lowercase strings.
|
|
151
|
-
- Lists are converted to comma-separated strings
|
|
160
|
+
- Lists are converted to comma-separated strings.
|
|
152
161
|
- Other types (strings, integers, dictionaries) are handled appropriately.
|
|
153
162
|
|
|
154
163
|
Args:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT.
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
-
import gzip
|
|
5
4
|
import hashlib
|
|
6
5
|
import hmac
|
|
7
6
|
import json
|
|
@@ -650,6 +649,11 @@ def verify_webhook_signature(body: bytes | str, signature: str, secret: str) ->
|
|
|
650
649
|
|
|
651
650
|
_GZIP_MAGIC = b"\x1f\x8b"
|
|
652
651
|
|
|
652
|
+
# Caps the decompressed size of a gzip webhook body. gunzip_payload runs before
|
|
653
|
+
# the signature check, so without a cap an unsigned gzip bomb could exhaust
|
|
654
|
+
# memory. Raise it if your events are larger.
|
|
655
|
+
MAX_WEBHOOK_PAYLOAD_BYTES = 10 * 1024 * 1024
|
|
656
|
+
|
|
653
657
|
|
|
654
658
|
def gunzip_payload(body: bytes) -> bytes:
|
|
655
659
|
"""Decompress body if gzip-prefixed, else pass through.
|
|
@@ -658,13 +662,8 @@ def gunzip_payload(body: bytes) -> bytes:
|
|
|
658
662
|
Stream webhook bodies are always JSON, and JSON cannot start with 0x1F.
|
|
659
663
|
|
|
660
664
|
Raises InvalidWebhookError if body has the gzip magic prefix but isn't a
|
|
661
|
-
valid gzip stream
|
|
662
|
-
|
|
663
|
-
* gzip.BadGzipFile (subclass of OSError) - malformed header.
|
|
664
|
-
* zlib.error - corrupt compressed stream.
|
|
665
|
-
* EOFError - stream truncated mid-block.
|
|
666
|
-
The first is OSError-derived; the latter two are not. Catch all three so
|
|
667
|
-
every "looks like gzip but isn't" failure mode surfaces as InvalidWebhookError.
|
|
665
|
+
valid gzip stream (corrupt or truncated), or decompresses to more than
|
|
666
|
+
MAX_WEBHOOK_PAYLOAD_BYTES.
|
|
668
667
|
"""
|
|
669
668
|
if not isinstance(body, (bytes, bytearray)):
|
|
670
669
|
raise InvalidWebhookError(
|
|
@@ -672,10 +671,25 @@ def gunzip_payload(body: bytes) -> bytes:
|
|
|
672
671
|
)
|
|
673
672
|
if len(body) < 2 or bytes(body[:2]) != _GZIP_MAGIC:
|
|
674
673
|
return bytes(body)
|
|
674
|
+
limit = MAX_WEBHOOK_PAYLOAD_BYTES
|
|
675
|
+
decompressor = zlib.decompressobj(wbits=31)
|
|
675
676
|
try:
|
|
676
|
-
|
|
677
|
-
except
|
|
677
|
+
out = decompressor.decompress(bytes(body), limit + 1)
|
|
678
|
+
except zlib.error as e:
|
|
678
679
|
raise InvalidWebhookError(f"{InvalidWebhookError.GZIP_FAILED}: {e}") from e
|
|
680
|
+
if len(out) > limit:
|
|
681
|
+
raise InvalidWebhookError(
|
|
682
|
+
f"{InvalidWebhookError.GZIP_FAILED}: payload exceeds {limit} bytes"
|
|
683
|
+
)
|
|
684
|
+
if not decompressor.eof:
|
|
685
|
+
raise InvalidWebhookError(
|
|
686
|
+
f"{InvalidWebhookError.GZIP_FAILED}: truncated gzip stream"
|
|
687
|
+
)
|
|
688
|
+
if decompressor.unused_data:
|
|
689
|
+
raise InvalidWebhookError(
|
|
690
|
+
f"{InvalidWebhookError.GZIP_FAILED}: trailing data after gzip stream"
|
|
691
|
+
)
|
|
692
|
+
return out
|
|
679
693
|
|
|
680
694
|
|
|
681
695
|
def decode_sqs_payload(message_body: str) -> bytes:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/01-setup-and-auth.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py
RENAMED
|
File without changes
|
{getstream-7.0.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|