getstream 3.3.1__tar.gz → 3.3.2__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 (118) hide show
  1. {getstream-3.3.1 → getstream-3.3.2}/PKG-INFO +1 -1
  2. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/connection_manager.py +40 -1
  3. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/reconnection.py +5 -2
  4. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/signaling.py +42 -0
  5. {getstream-3.3.1 → getstream-3.3.2}/.cursor/worktrees.json +0 -0
  6. {getstream-3.3.1 → getstream-3.3.2}/.env.example +0 -0
  7. {getstream-3.3.1 → getstream-3.3.2}/.github/actions/python-uv-setup/action.yml +0 -0
  8. {getstream-3.3.1 → getstream-3.3.2}/.github/workflows/ci.yml +0 -0
  9. {getstream-3.3.1 → getstream-3.3.2}/.github/workflows/release.yml +0 -0
  10. {getstream-3.3.1 → getstream-3.3.2}/.github/workflows/run_tests.yml +0 -0
  11. {getstream-3.3.1 → getstream-3.3.2}/.github/workflows/stream-py.code-workspace +0 -0
  12. {getstream-3.3.1 → getstream-3.3.2}/.gitignore +0 -0
  13. {getstream-3.3.1 → getstream-3.3.2}/.gitmodules +0 -0
  14. {getstream-3.3.1 → getstream-3.3.2}/.pre-commit-config.yaml +0 -0
  15. {getstream-3.3.1 → getstream-3.3.2}/AGENTS.md +0 -0
  16. {getstream-3.3.1 → getstream-3.3.2}/CHANGELOG.md +0 -0
  17. {getstream-3.3.1 → getstream-3.3.2}/DEVELOPMENT.md +0 -0
  18. {getstream-3.3.1 → getstream-3.3.2}/LICENSE.md +0 -0
  19. {getstream-3.3.1 → getstream-3.3.2}/MIGRATION_v2_to_v3.md +0 -0
  20. {getstream-3.3.1 → getstream-3.3.2}/Makefile +0 -0
  21. {getstream-3.3.1 → getstream-3.3.2}/README.md +0 -0
  22. {getstream-3.3.1 → getstream-3.3.2}/dev.py +0 -0
  23. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/01-setup-and-auth.md +0 -0
  24. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/02-users.md +0 -0
  25. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/03-channels.md +0 -0
  26. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/04-messages-and-reactions.md +0 -0
  27. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/05-moderation.md +0 -0
  28. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/06-devices.md +0 -0
  29. {getstream-3.3.1 → getstream-3.3.2}/docs/migration-from-stream-chat-python/README.md +0 -0
  30. {getstream-3.3.1 → getstream-3.3.2}/generate.sh +0 -0
  31. {getstream-3.3.1 → getstream-3.3.2}/generate_webrtc.sh +0 -0
  32. {getstream-3.3.1 → getstream-3.3.2}/getstream/__init__.py +0 -0
  33. {getstream-3.3.1 → getstream-3.3.2}/getstream/base.py +0 -0
  34. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/__init__.py +0 -0
  35. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/async_channel.py +0 -0
  36. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/async_client.py +0 -0
  37. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/async_rest_client.py +0 -0
  38. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/channel.py +0 -0
  39. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/client.py +0 -0
  40. {getstream-3.3.1 → getstream-3.3.2}/getstream/chat/rest_client.py +0 -0
  41. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/__init__.py +0 -0
  42. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/async_client.py +0 -0
  43. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/async_rest_client.py +0 -0
  44. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/client.py +0 -0
  45. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/rest_client.py +0 -0
  46. {getstream-3.3.1 → getstream-3.3.2}/getstream/common/telemetry.py +0 -0
  47. {getstream-3.3.1 → getstream-3.3.2}/getstream/config.py +0 -0
  48. {getstream-3.3.1 → getstream-3.3.2}/getstream/feeds/__init__.py +0 -0
  49. {getstream-3.3.1 → getstream-3.3.2}/getstream/feeds/client.py +0 -0
  50. {getstream-3.3.1 → getstream-3.3.2}/getstream/feeds/feeds.py +0 -0
  51. {getstream-3.3.1 → getstream-3.3.2}/getstream/feeds/rest_client.py +0 -0
  52. {getstream-3.3.1 → getstream-3.3.2}/getstream/generic.py +0 -0
  53. {getstream-3.3.1 → getstream-3.3.2}/getstream/meta.py +0 -0
  54. {getstream-3.3.1 → getstream-3.3.2}/getstream/models/__init__.py +0 -0
  55. {getstream-3.3.1 → getstream-3.3.2}/getstream/moderation/__init__.py +0 -0
  56. {getstream-3.3.1 → getstream-3.3.2}/getstream/moderation/async_client.py +0 -0
  57. {getstream-3.3.1 → getstream-3.3.2}/getstream/moderation/async_rest_client.py +0 -0
  58. {getstream-3.3.1 → getstream-3.3.2}/getstream/moderation/client.py +0 -0
  59. {getstream-3.3.1 → getstream-3.3.2}/getstream/moderation/rest_client.py +0 -0
  60. {getstream-3.3.1 → getstream-3.3.2}/getstream/rate_limit.py +0 -0
  61. {getstream-3.3.1 → getstream-3.3.2}/getstream/stream.py +0 -0
  62. {getstream-3.3.1 → getstream-3.3.2}/getstream/stream_response.py +0 -0
  63. {getstream-3.3.1 → getstream-3.3.2}/getstream/tests/test_webhook.py +0 -0
  64. {getstream-3.3.1 → getstream-3.3.2}/getstream/utils/__init__.py +0 -0
  65. {getstream-3.3.1 → getstream-3.3.2}/getstream/utils/event_emitter.py +0 -0
  66. {getstream-3.3.1 → getstream-3.3.2}/getstream/utils/retry.py +0 -0
  67. {getstream-3.3.1 → getstream-3.3.2}/getstream/version.py +0 -0
  68. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/__init__.py +0 -0
  69. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/async_call.py +0 -0
  70. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/async_client.py +0 -0
  71. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/async_rest_client.py +0 -0
  72. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/call.py +0 -0
  73. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/client.py +0 -0
  74. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/openai.py +0 -0
  75. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rest_client.py +0 -0
  76. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/README.md +0 -0
  77. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/__init__.py +0 -0
  78. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/audio_track.py +0 -0
  79. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/connection_utils.py +0 -0
  80. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/coordinator/__init__.py +0 -0
  81. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/coordinator/backoff.py +0 -0
  82. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/coordinator/errors.py +0 -0
  83. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/coordinator/ws.py +0 -0
  84. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/coordinator_api.py +0 -0
  85. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/encoders_patches.py +0 -0
  86. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/g711.py +0 -0
  87. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/location_discovery.py +0 -0
  88. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/models.py +0 -0
  89. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/network_monitor.py +0 -0
  90. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/participants.py +0 -0
  91. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/__init__.py +0 -0
  92. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/__init__.py +0 -0
  93. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/__init__.py +0 -0
  94. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/__init__.py +0 -0
  95. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/event/__init__.py +0 -0
  96. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.py +0 -0
  97. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/event/events_pb2.pyi +0 -0
  98. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/models/__init__.py +0 -0
  99. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.py +0 -0
  100. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/models/models_pb2.pyi +0 -0
  101. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/__init__.py +0 -0
  102. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.py +0 -0
  103. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_pb2.pyi +0 -0
  104. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pb/stream/video/sfu/signal_rpc/signal_twirp.py +0 -0
  105. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/pc.py +0 -0
  106. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/peer_connection.py +0 -0
  107. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/recording.py +0 -0
  108. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/stats_reporter.py +0 -0
  109. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/stats_tracer.py +0 -0
  110. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/tracer.py +0 -0
  111. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/track_util.py +0 -0
  112. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/tracks.py +0 -0
  113. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/twirp_client_wrapper.py +0 -0
  114. {getstream-3.3.1 → getstream-3.3.2}/getstream/video/rtc/utils.py +0 -0
  115. {getstream-3.3.1 → getstream-3.3.2}/getstream/webhook.py +0 -0
  116. {getstream-3.3.1 → getstream-3.3.2}/pyproject.toml +0 -0
  117. {getstream-3.3.1 → getstream-3.3.2}/pytest.ini +0 -0
  118. {getstream-3.3.1 → getstream-3.3.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: getstream
3
- Version: 3.3.1
3
+ Version: 3.3.2
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
@@ -36,7 +36,7 @@ from getstream.video.rtc.network_monitor import NetworkMonitor
36
36
  from getstream.video.rtc.recording import RecordingManager
37
37
  from getstream.video.rtc.participants import ParticipantsState
38
38
  from getstream.video.rtc.tracks import SubscriptionConfig, SubscriptionManager
39
- from getstream.video.rtc.reconnection import ReconnectionManager
39
+ from getstream.video.rtc.reconnection import ReconnectionManager, ReconnectionStrategy
40
40
  from getstream.video.rtc.peer_connection import PeerConnectionManager
41
41
  from getstream.video.rtc.models import JoinCallResponse
42
42
  from getstream.video.rtc.tracer import Tracer
@@ -198,6 +198,17 @@ class ConnectionManager(StreamAsyncIOEventEmitter):
198
198
  async def _on_subscriber_offer(self, event: events_pb2.SubscriberOffer):
199
199
  logger.info("Subscriber offer received")
200
200
 
201
+ # Offers can arrive after the subscriber peer connection has been
202
+ # torn down (slow asyncio loop under load, SFU sending a late
203
+ # renegotiation). `setRemoteDescription` would raise
204
+ # `InvalidStateError: Cannot handle offer in signaling state "closed"`
205
+ # and the exception propagates through the pyee error path, killing
206
+ # the session. Drop the offer instead — there is nothing to
207
+ # negotiate with a closed connection.
208
+ if self.subscriber_pc is None or self.subscriber_pc.signalingState == "closed":
209
+ logger.debug("Subscriber offer arrived after PC closed; dropping")
210
+ return
211
+
201
212
  with telemetry.start_as_current_span("rtc.on_subscriber_offer") as span:
202
213
  await self.subscriber_negotiation_lock.acquire()
203
214
 
@@ -266,6 +277,25 @@ class ConnectionManager(StreamAsyncIOEventEmitter):
266
277
  finally:
267
278
  self.subscriber_negotiation_lock.release()
268
279
 
280
+ async def _on_signaling_connection_lost(self, reason: str) -> None:
281
+ """Reconnect when the signaling WebSocket drops unexpectedly.
282
+
283
+ The WebSocketClient itself only logs the error and stops; it has
284
+ no reconnect of its own. This handler bridges that gap by routing
285
+ the loss into the existing `ReconnectionManager`, so a transient
286
+ TCP reset or a missed health check no longer means a dead session.
287
+ """
288
+ if not self.running:
289
+ return
290
+ logger.warning(f"Signaling WS lost; triggering reconnect: {reason}")
291
+ try:
292
+ await self._reconnector.reconnect(
293
+ strategy=ReconnectionStrategy.FAST,
294
+ reason=f"signaling ws lost: {reason}",
295
+ )
296
+ except Exception:
297
+ logger.exception("Reconnect after signaling WS loss failed")
298
+
269
299
  async def _connect_coordinator_ws(self):
270
300
  """
271
301
  Connects to the coordinator websocket and subscribes to events.
@@ -403,6 +433,15 @@ class ConnectionManager(StreamAsyncIOEventEmitter):
403
433
  # Connect subscriber offer event to handle SDP negotiation
404
434
  self._ws_client.on_event("subscriber_offer", self._on_subscriber_offer)
405
435
 
436
+ # Drive reconnection when the signaling WS drops outside of an
437
+ # SFU-level error event (raw socket close, health-check timeout,
438
+ # transport-level exceptions). Without this handler the
439
+ # WebSocketClient just logs and stops; the session sits hanging
440
+ # until the frontend times out and tears it down.
441
+ self._ws_client.on_event(
442
+ "connection_lost", self._on_signaling_connection_lost
443
+ )
444
+
406
445
  # Re-emit the events so they can be subscribed to on the ConnectionManager
407
446
  self._ws_client.on_wildcard("*", self.emit)
408
447
 
@@ -209,10 +209,13 @@ class ReconnectionManager:
209
209
  self.connection_manager._connection_options.fast_reconnect = True
210
210
  previous_ws_client = self.connection_manager.ws_client
211
211
 
212
- # Use _connect_internal with existing connection info
212
+ # Use _connect_internal with existing connection info.
213
+ # `self.join_response` is already the unwrapped data payload
214
+ # (see ConnectionManager._connect_internal: it stores
215
+ # `join_response.data`), so credentials live at the top level.
213
216
  await self.connection_manager._connect_internal(
214
217
  region=self.connection_manager._connection_options.region,
215
- token=self.connection_manager.join_response.data.credentials.token
218
+ token=self.connection_manager.join_response.credentials.token
216
219
  if self.connection_manager.join_response
217
220
  else None,
218
221
  session_id=self.connection_manager.session_id,
@@ -67,6 +67,7 @@ class WebSocketClient(StreamAsyncIOEventEmitter):
67
67
  self.thread = None
68
68
  self.running = False
69
69
  self.closed = False
70
+ self._connection_lost_sent = False
70
71
 
71
72
  # For ping/health check mechanism
72
73
  self.ping_thread = None
@@ -214,11 +215,48 @@ class WebSocketClient(StreamAsyncIOEventEmitter):
214
215
  error_event.error.error.message = str(error)
215
216
  self.first_message = error_event
216
217
  self.first_message_event.set()
218
+ elif not self.closed:
219
+ self._notify_connection_lost(f"error: {error}")
217
220
 
218
221
  def _on_close(self, ws, close_status_code, close_msg):
219
222
  """Handle WebSocket close event."""
220
223
  logger.debug(f"WebSocket connection closed: {close_status_code} {close_msg}")
224
+ was_unexpected = not self.closed
221
225
  self.running = False
226
+ if was_unexpected:
227
+ self._notify_connection_lost(
228
+ f"closed by remote (code={close_status_code} msg={close_msg})"
229
+ )
230
+
231
+ def _notify_connection_lost(self, reason: str) -> None:
232
+ """Schedule a ``connection_lost`` emit on the main loop.
233
+
234
+ Idempotent per ``WebSocketClient`` instance — only the first call
235
+ per disconnect actually emits. Callers run on the WS worker thread
236
+ or ``_ping_loop`` thread; pyee schedules async listeners via
237
+ ``loop.create_task``, which is not thread-safe, hence the hop.
238
+ Same pattern as ``_on_message`` for SFU events.
239
+ """
240
+ if not self._claim_connection_lost():
241
+ return
242
+ try:
243
+ asyncio.run_coroutine_threadsafe(
244
+ self._emit_connection_lost(reason),
245
+ self.main_loop,
246
+ )
247
+ except Exception:
248
+ logger.exception("Failed to schedule connection_lost emit")
249
+
250
+ def _claim_connection_lost(self) -> bool:
251
+ """Return True iff this is the first connection-lost notification."""
252
+ if self._connection_lost_sent:
253
+ return False
254
+ self._connection_lost_sent = True
255
+ return True
256
+
257
+ async def _emit_connection_lost(self, reason: str) -> None:
258
+ with telemetry.attach_span(self.parent_span):
259
+ self.emit("connection_lost", reason)
222
260
 
223
261
  def _start_ping_handler(self):
224
262
  """Start the ping mechanism in a background thread."""
@@ -242,6 +280,10 @@ class WebSocketClient(StreamAsyncIOEventEmitter):
242
280
  current_time = time.time()
243
281
  if current_time - self.last_health_check_time > self.ping_interval * 2:
244
282
  logger.warning("Health check failed, closing connection")
283
+ # Notify before close() so the owner can reconnect; close()
284
+ # itself sets `self.closed=True` and would suppress the
285
+ # notification in `_on_close`.
286
+ self._notify_connection_lost("health check timeout")
245
287
  self.close()
246
288
  return
247
289
 
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