getstream 7.1.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.
Files changed (110) hide show
  1. {getstream-7.1.0 → getstream-7.2.0}/.release-please-manifest.json +1 -1
  2. {getstream-7.1.0 → getstream-7.2.0}/CHANGELOG.md +13 -0
  3. {getstream-7.1.0 → getstream-7.2.0}/PKG-INFO +1 -1
  4. {getstream-7.1.0 → getstream-7.2.0}/getstream/models/__init__.py +12 -0
  5. {getstream-7.1.0 → getstream-7.2.0}/getstream/tests/test_webhook.py +12 -0
  6. {getstream-7.1.0 → getstream-7.2.0}/getstream/utils/__init__.py +3 -4
  7. {getstream-7.1.0 → getstream-7.2.0}/getstream/webhook.py +24 -10
  8. {getstream-7.1.0 → getstream-7.2.0}/pyproject.toml +1 -1
  9. {getstream-7.1.0 → getstream-7.2.0}/uv.lock +1 -1
  10. {getstream-7.1.0 → getstream-7.2.0}/.cursor/worktrees.json +0 -0
  11. {getstream-7.1.0 → getstream-7.2.0}/.env.example +0 -0
  12. {getstream-7.1.0 → getstream-7.2.0}/.github/actions/build-rtc-wheel/action.yml +0 -0
  13. {getstream-7.1.0 → getstream-7.2.0}/.github/actions/python-uv-setup/action.yml +0 -0
  14. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/ci.yml +0 -0
  15. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/pr_title.yml +0 -0
  16. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/release.yml +0 -0
  17. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/rtc_wheels.yml +0 -0
  18. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/run_integration.yml +0 -0
  19. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/run_tests.yml +0 -0
  20. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/scheduled_test.yml +0 -0
  21. {getstream-7.1.0 → getstream-7.2.0}/.github/workflows/stream-py.code-workspace +0 -0
  22. {getstream-7.1.0 → getstream-7.2.0}/.gitignore +0 -0
  23. {getstream-7.1.0 → getstream-7.2.0}/.gitmodules +0 -0
  24. {getstream-7.1.0 → getstream-7.2.0}/.pre-commit-config.yaml +0 -0
  25. {getstream-7.1.0 → getstream-7.2.0}/AGENTS.md +0 -0
  26. {getstream-7.1.0 → getstream-7.2.0}/DEVELOPMENT.md +0 -0
  27. {getstream-7.1.0 → getstream-7.2.0}/LICENSE.md +0 -0
  28. {getstream-7.1.0 → getstream-7.2.0}/MIGRATION_v2_to_v3.md +0 -0
  29. {getstream-7.1.0 → getstream-7.2.0}/MIGRATION_v6_to_v7.md +0 -0
  30. {getstream-7.1.0 → getstream-7.2.0}/Makefile +0 -0
  31. {getstream-7.1.0 → getstream-7.2.0}/README.md +0 -0
  32. {getstream-7.1.0 → getstream-7.2.0}/dev.py +0 -0
  33. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/01-setup-and-auth.md +0 -0
  34. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/02-users.md +0 -0
  35. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/03-channels.md +0 -0
  36. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/04-messages-and-reactions.md +0 -0
  37. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/05-moderation.md +0 -0
  38. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/06-devices.md +0 -0
  39. {getstream-7.1.0 → getstream-7.2.0}/docs/migration-from-stream-chat-python/README.md +0 -0
  40. {getstream-7.1.0 → getstream-7.2.0}/generate.sh +0 -0
  41. {getstream-7.1.0 → getstream-7.2.0}/generate_webrtc.sh +0 -0
  42. {getstream-7.1.0 → getstream-7.2.0}/getstream/__init__.py +0 -0
  43. {getstream-7.1.0 → getstream-7.2.0}/getstream/base.py +0 -0
  44. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/__init__.py +0 -0
  45. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/async_channel.py +0 -0
  46. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/async_client.py +0 -0
  47. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/async_rest_client.py +0 -0
  48. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/channel.py +0 -0
  49. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/client.py +0 -0
  50. {getstream-7.1.0 → getstream-7.2.0}/getstream/chat/rest_client.py +0 -0
  51. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/__init__.py +0 -0
  52. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/async_client.py +0 -0
  53. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/async_rest_client.py +0 -0
  54. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/client.py +0 -0
  55. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/rest_client.py +0 -0
  56. {getstream-7.1.0 → getstream-7.2.0}/getstream/common/telemetry.py +0 -0
  57. {getstream-7.1.0 → getstream-7.2.0}/getstream/config.py +0 -0
  58. {getstream-7.1.0 → getstream-7.2.0}/getstream/exceptions.py +0 -0
  59. {getstream-7.1.0 → getstream-7.2.0}/getstream/feeds/__init__.py +0 -0
  60. {getstream-7.1.0 → getstream-7.2.0}/getstream/feeds/client.py +0 -0
  61. {getstream-7.1.0 → getstream-7.2.0}/getstream/feeds/feeds.py +0 -0
  62. {getstream-7.1.0 → getstream-7.2.0}/getstream/feeds/rest_client.py +0 -0
  63. {getstream-7.1.0 → getstream-7.2.0}/getstream/generic.py +0 -0
  64. {getstream-7.1.0 → getstream-7.2.0}/getstream/logging_utils.py +0 -0
  65. {getstream-7.1.0 → getstream-7.2.0}/getstream/meta.py +0 -0
  66. {getstream-7.1.0 → getstream-7.2.0}/getstream/moderation/__init__.py +0 -0
  67. {getstream-7.1.0 → getstream-7.2.0}/getstream/moderation/async_client.py +0 -0
  68. {getstream-7.1.0 → getstream-7.2.0}/getstream/moderation/async_rest_client.py +0 -0
  69. {getstream-7.1.0 → getstream-7.2.0}/getstream/moderation/client.py +0 -0
  70. {getstream-7.1.0 → getstream-7.2.0}/getstream/moderation/rest_client.py +0 -0
  71. {getstream-7.1.0 → getstream-7.2.0}/getstream/rate_limit.py +0 -0
  72. {getstream-7.1.0 → getstream-7.2.0}/getstream/stream.py +0 -0
  73. {getstream-7.1.0 → getstream-7.2.0}/getstream/stream_response.py +0 -0
  74. {getstream-7.1.0 → getstream-7.2.0}/getstream/tasks.py +0 -0
  75. {getstream-7.1.0 → getstream-7.2.0}/getstream/utils/event_emitter.py +0 -0
  76. {getstream-7.1.0 → getstream-7.2.0}/getstream/version.py +0 -0
  77. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/__init__.py +0 -0
  78. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/async_call.py +0 -0
  79. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/async_client.py +0 -0
  80. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/async_rest_client.py +0 -0
  81. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/call.py +0 -0
  82. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/client.py +0 -0
  83. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/openai.py +0 -0
  84. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rest_client.py +0 -0
  85. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/__init__.py +0 -0
  86. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/audio_forwarder.py +0 -0
  87. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/audio_track.py +0 -0
  88. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/connection_manager.py +0 -0
  89. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/g711.py +0 -0
  90. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/participants.py +0 -0
  91. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/__init__.py +0 -0
  92. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/__init__.py +0 -0
  93. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/__init__.py +0 -0
  94. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/__init__.py +0 -0
  95. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py +0 -0
  96. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py +0 -0
  97. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi +0 -0
  98. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py +0 -0
  99. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py +0 -0
  100. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi +0 -0
  101. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py +0 -0
  102. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py +0 -0
  103. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.pyi +0 -0
  104. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_twirp.py +0 -0
  105. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/track_util.py +0 -0
  106. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/tracks.py +0 -0
  107. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/utils.py +0 -0
  108. {getstream-7.1.0 → getstream-7.2.0}/getstream/video/rtc/video_forwarder.py +0 -0
  109. {getstream-7.1.0 → getstream-7.2.0}/pytest.ini +0 -0
  110. {getstream-7.1.0 → getstream-7.2.0}/release-please-config.json +0 -0
@@ -1,4 +1,4 @@
1
1
  {
2
- ".": "7.1.0",
2
+ ".": "7.2.0",
3
3
  "getstream-rtc": "0.1.0"
4
4
  }
@@ -7,6 +7,19 @@ 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
+
10
23
  ## [7.1.0](https://github.com/GetStream/stream-py/compare/v7.0.0...v7.1.0) (2026-11-04)
11
24
 
12
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: getstream
3
- Version: 7.1.0
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
@@ -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
@@ -2,7 +2,6 @@ import enum
2
2
  import json
3
3
  import asyncio
4
4
  from typing import Dict, List, Optional, Union, cast
5
- from urllib.parse import quote
6
5
  from datetime import datetime
7
6
  from datetime import timezone
8
7
  from urllib.parse import urlparse, urlunparse
@@ -120,7 +119,7 @@ def build_query_param(**kwargs):
120
119
  - JSON-serializable objects with a `to_json` method will be serialized using that method.
121
120
  - datetime objects are converted to ISO 8601 strings.
122
121
  - Booleans are converted to lowercase strings.
123
- - Lists are converted to comma-separated strings with URL-encoded values.
122
+ - Lists are converted to comma-separated strings.
124
123
  - Other types (strings, integers, dictionaries) are handled appropriately.
125
124
 
126
125
  Args:
@@ -142,7 +141,7 @@ def build_query_param(**kwargs):
142
141
  elif isinstance(value, (str, int)):
143
142
  params[key] = str(value)
144
143
  elif isinstance(value, list):
145
- params[key] = ",".join(quote(_serialize_query_value(v)) for v in value)
144
+ params[key] = ",".join(_serialize_query_value(v) for v in value)
146
145
  else:
147
146
  # For dictionaries or any other types of objects
148
147
  params[key] = json.dumps(value)
@@ -158,7 +157,7 @@ async def build_query_param_async(**kwargs):
158
157
  This function handles various data types:
159
158
  - JSON-serializable objects with a `to_json` method will be serialized using that method.
160
159
  - Booleans are converted to lowercase strings.
161
- - Lists are converted to comma-separated strings with URL-encoded values.
160
+ - Lists are converted to comma-separated strings.
162
161
  - Other types (strings, integers, dictionaries) are handled appropriately.
163
162
 
164
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. gzip.decompress can fail with at least three different
662
- exception types depending on where decoding breaks:
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
- return gzip.decompress(bytes(body))
677
- except (OSError, zlib.error, EOFError) as e:
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:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "getstream"
3
- version = "7.1.0"
3
+ version = "7.2.0"
4
4
  description = "GetStream Python SDK - Build scalable activity feeds, chat, and video calling applications"
5
5
  authors = [
6
6
  { name = "sachaarbonel", email = "sacha.arbonel@hotmail.fr" },
@@ -863,7 +863,7 @@ wheels = [
863
863
 
864
864
  [[package]]
865
865
  name = "getstream"
866
- version = "7.1.0"
866
+ version = "7.2.0"
867
867
  source = { editable = "." }
868
868
  dependencies = [
869
869
  { name = "dataclasses-json" },
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