phoenix-channels-python-client 0.4.1__tar.gz → 0.4.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.
- {phoenix_channels_python_client-0.4.1/phoenix_channels_python_client.egg-info → phoenix_channels_python_client-0.4.2}/PKG-INFO +72 -5
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/README.md +69 -2
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/__init__.py +3 -1
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/client.py +4 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/client_types.py +4 -1
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/topic_runtime.py +181 -45
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/topic_subscription.py +6 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2/phoenix_channels_python_client.egg-info}/PKG-INFO +72 -5
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client.egg-info/SOURCES.txt +6 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client.egg-info/requires.txt +2 -2
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/pyproject.toml +3 -3
- phoenix_channels_python_client-0.4.2/tests/test_cleanup_ownership.py +95 -0
- phoenix_channels_python_client-0.4.2/tests/test_join_installation.py +428 -0
- phoenix_channels_python_client-0.4.2/tests/test_join_lifecycle.py +733 -0
- phoenix_channels_python_client-0.4.2/tests/test_join_responses.py +203 -0
- phoenix_channels_python_client-0.4.2/tests/test_optional_lifecycle.py +582 -0
- phoenix_channels_python_client-0.4.2/tests/test_optional_reconnect.py +249 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_topic_runtime_internals.py +11 -7
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/LICENSE +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/MANIFEST.in +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/authentication.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/client_state_machine.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/diagnostics.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/exceptions.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/phx_messages.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/protocol_handler.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/reconnect_controller.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/shutdown_signals.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/supervisor.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client/utils.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client.egg-info/dependency_links.txt +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/phoenix_channels_python_client.egg-info/top_level.txt +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/setup.cfg +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_cancel_and_wait.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_channel_recovery.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_client_state.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_connection.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_heartbeat.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_heartbeat_timeout.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_protocol_handler.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_reconnect.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_reconnect_policy_invariants.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_reconnect_stress.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_run_forever_signals.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_security.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_shutdown_races.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_subscriptions.py +0 -0
- {phoenix_channels_python_client-0.4.1 → phoenix_channels_python_client-0.4.2}/tests/test_supervisor_internals.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phoenix-channels-python-client
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A Python client library for connecting to Phoenix Channels
|
|
5
5
|
Author: Phoenix Channels Python Client
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Dist: websockets>=14.2
|
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pytest; extra == "dev"
|
|
21
21
|
Requires-Dist: pytest-asyncio>=1.4; extra == "dev"
|
|
22
|
-
Requires-Dist: looptime<0.
|
|
22
|
+
Requires-Dist: looptime<0.10,>=0.7; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
24
24
|
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
25
25
|
Requires-Dist: commitizen>=3.13.0; extra == "dev"
|
|
@@ -28,7 +28,7 @@ Requires-Dist: ruff; extra == "dev"
|
|
|
28
28
|
Provides-Extra: test
|
|
29
29
|
Requires-Dist: pytest; extra == "test"
|
|
30
30
|
Requires-Dist: pytest-asyncio>=1.4; extra == "test"
|
|
31
|
-
Requires-Dist: looptime<0.
|
|
31
|
+
Requires-Dist: looptime<0.10,>=0.7; extra == "test"
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
34
34
|
# Phoenix Channels Python Client
|
|
@@ -303,13 +303,14 @@ Everything after `api_key` is keyword-only:
|
|
|
303
303
|
| `leave_timeout_s` | `5.0` | Wait for a leave reply |
|
|
304
304
|
| `max_topic_queue_size` | `1000` | Per-topic buffer; the oldest message is dropped when full |
|
|
305
305
|
| `callback_drain_timeout_s` | `2.0` | How long the message being handled (its message handler, then its event handler) may finish before a rejoin cancels it; queued messages are dropped |
|
|
306
|
-
| `on_reconnect` | `None` | `async () -> None`, called after
|
|
306
|
+
| `on_reconnect` | `None` | `async () -> None`, called after required first-attempt reconciliation |
|
|
307
307
|
| `on_disconnect` | `None` | `async (error: Exception \| None) -> None`; `None` on a clean close |
|
|
308
308
|
| `on_heartbeat_ack` | `None` | Synchronous `() -> None`; runs on the message path, so keep it fast |
|
|
309
309
|
| `on_topic_lost` | `None` | `async (topic: str, error: Exception) -> None`, called when the client drops a topic you didn't unsubscribe |
|
|
310
310
|
| `additional_headers` | `None` | Extra handshake headers sent on every (re)connect |
|
|
311
311
|
|
|
312
|
-
Exceptions raised in callbacks are logged, not raised.
|
|
312
|
+
Exceptions raised in ordinary message and lifecycle callbacks are logged, not raised.
|
|
313
|
+
Join-response installation failures instead fail the join (see below).
|
|
313
314
|
|
|
314
315
|
## Connection Lifecycle and Errors
|
|
315
316
|
|
|
@@ -419,3 +420,69 @@ headers, frame bodies, user/topic identities, and untrusted server error details
|
|
|
419
420
|
Logs retain static lifecycle descriptions, native connection states,
|
|
420
421
|
and exception types. Application data and caller-visible errors are not changed;
|
|
421
422
|
applications remain responsible for logs outside this connection-owned boundary.
|
|
423
|
+
|
|
424
|
+
### Ordered join responses
|
|
425
|
+
|
|
426
|
+
Use the optional keyword-only `on_join_response` hook to install a snapshot from
|
|
427
|
+
initial joins, socket rejoins and same-socket channel recovery:
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
state = None
|
|
431
|
+
|
|
432
|
+
def install_snapshot(response):
|
|
433
|
+
global state
|
|
434
|
+
state = validate_snapshot(response)
|
|
435
|
+
|
|
436
|
+
await client.subscribe_to_topic(
|
|
437
|
+
"room:123", on_message, on_join_response=install_snapshot
|
|
438
|
+
)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`JoinResponseCallback` is exported from the package. The hook receives the decoded
|
|
442
|
+
successful `payload.response`; a missing response is `None`. The transport adds no
|
|
443
|
+
application schema or empty snapshot. Installation runs synchronously before
|
|
444
|
+
`is_topic_joined()` becomes true, subscribe completes, or later deltas deliver.
|
|
445
|
+
Subscribe returns `None`. Success admits the subscription; a later disconnect or
|
|
446
|
+
channel failure can already have changed its current readiness when the caller
|
|
447
|
+
resumes.
|
|
448
|
+
|
|
449
|
+
Hooks must be short, nonblocking and return `None`. Do not await, perform I/O,
|
|
450
|
+
wait on locks, defer installation, or change task/client lifecycle inside them.
|
|
451
|
+
Async callables, awaitable results and other non-`None` results fail admission.
|
|
452
|
+
The join timeout bounds reply waiting and cannot preempt synchronous code. Hook
|
|
453
|
+
exceptions (including directly raised `CancelledError`) fail with `PHXTopicError`
|
|
454
|
+
and preserve their cause; arbitrary prior hook effects are not rolled back.
|
|
455
|
+
Initial failure belongs to the caller. An established subscription whose installer
|
|
456
|
+
fails is removed and reported once through `on_topic_lost`.
|
|
457
|
+
|
|
458
|
+
Initial cancellation, timeout and leave remove the exact subscription. Physical
|
|
459
|
+
cleanup remains owned if its waiter is canceled again, and shutdown awaits it.
|
|
460
|
+
Never-established subscriptions are not adopted by automatic recovery. Recorded
|
|
461
|
+
queue loss before a hook-enabled installation fails the attempt transiently;
|
|
462
|
+
steady-state queues still drop their oldest message at capacity. There is no
|
|
463
|
+
replay guarantee. A fresh client must install a fresh snapshot after process loss.
|
|
464
|
+
|
|
465
|
+
Join replies match request refs on both protocols. V2 also filters stale channel
|
|
466
|
+
generations by `join_ref`. V1 cannot reliably fence unsolicited events/lifecycle
|
|
467
|
+
frames from older channels; it does not provide V2 event-generation isolation.
|
|
468
|
+
|
|
469
|
+
### Optional reconnect participation
|
|
470
|
+
|
|
471
|
+
`subscribe_to_topic(topic, async_callback=None, *, on_join_response=None,
|
|
472
|
+
blocks_reconnect=True) -> None` preserves required recovery by default. Required
|
|
473
|
+
topics reconcile their first socket-rejoin attempt sequentially before
|
|
474
|
+
`on_reconnect`; transient failures continue through existing recovery/backoff.
|
|
475
|
+
The barrier does not wait for eventual success of every required topic.
|
|
476
|
+
|
|
477
|
+
Set `blocks_reconnect=False` for optional subscriptions. After required first
|
|
478
|
+
attempts, their existing per-topic recovery tasks perform an immediate join and
|
|
479
|
+
retry transient failures with the existing backoff. Required event delivery and
|
|
480
|
+
`on_reconnect` do not wait for optional replies, old callback drains or retries.
|
|
481
|
+
This option cannot isolate synchronous code that blocks the event loop. Explicit
|
|
482
|
+
initial subscribe always waits for its own join response and installation.
|
|
483
|
+
|
|
484
|
+
`on_reconnect` conveys no optional readiness or registration-absence guarantee.
|
|
485
|
+
Check `is_topic_joined()` for current readiness and use `on_topic_lost` to replace
|
|
486
|
+
permanently rejected optional subscriptions; do not blindly resubscribe them
|
|
487
|
+
from `on_reconnect`. Leave, shutdown and another socket disconnect cancel/await
|
|
488
|
+
the owned recovery; obsolete generations cannot install or revive a replacement.
|
|
@@ -270,13 +270,14 @@ Everything after `api_key` is keyword-only:
|
|
|
270
270
|
| `leave_timeout_s` | `5.0` | Wait for a leave reply |
|
|
271
271
|
| `max_topic_queue_size` | `1000` | Per-topic buffer; the oldest message is dropped when full |
|
|
272
272
|
| `callback_drain_timeout_s` | `2.0` | How long the message being handled (its message handler, then its event handler) may finish before a rejoin cancels it; queued messages are dropped |
|
|
273
|
-
| `on_reconnect` | `None` | `async () -> None`, called after
|
|
273
|
+
| `on_reconnect` | `None` | `async () -> None`, called after required first-attempt reconciliation |
|
|
274
274
|
| `on_disconnect` | `None` | `async (error: Exception \| None) -> None`; `None` on a clean close |
|
|
275
275
|
| `on_heartbeat_ack` | `None` | Synchronous `() -> None`; runs on the message path, so keep it fast |
|
|
276
276
|
| `on_topic_lost` | `None` | `async (topic: str, error: Exception) -> None`, called when the client drops a topic you didn't unsubscribe |
|
|
277
277
|
| `additional_headers` | `None` | Extra handshake headers sent on every (re)connect |
|
|
278
278
|
|
|
279
|
-
Exceptions raised in callbacks are logged, not raised.
|
|
279
|
+
Exceptions raised in ordinary message and lifecycle callbacks are logged, not raised.
|
|
280
|
+
Join-response installation failures instead fail the join (see below).
|
|
280
281
|
|
|
281
282
|
## Connection Lifecycle and Errors
|
|
282
283
|
|
|
@@ -386,3 +387,69 @@ headers, frame bodies, user/topic identities, and untrusted server error details
|
|
|
386
387
|
Logs retain static lifecycle descriptions, native connection states,
|
|
387
388
|
and exception types. Application data and caller-visible errors are not changed;
|
|
388
389
|
applications remain responsible for logs outside this connection-owned boundary.
|
|
390
|
+
|
|
391
|
+
### Ordered join responses
|
|
392
|
+
|
|
393
|
+
Use the optional keyword-only `on_join_response` hook to install a snapshot from
|
|
394
|
+
initial joins, socket rejoins and same-socket channel recovery:
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
state = None
|
|
398
|
+
|
|
399
|
+
def install_snapshot(response):
|
|
400
|
+
global state
|
|
401
|
+
state = validate_snapshot(response)
|
|
402
|
+
|
|
403
|
+
await client.subscribe_to_topic(
|
|
404
|
+
"room:123", on_message, on_join_response=install_snapshot
|
|
405
|
+
)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`JoinResponseCallback` is exported from the package. The hook receives the decoded
|
|
409
|
+
successful `payload.response`; a missing response is `None`. The transport adds no
|
|
410
|
+
application schema or empty snapshot. Installation runs synchronously before
|
|
411
|
+
`is_topic_joined()` becomes true, subscribe completes, or later deltas deliver.
|
|
412
|
+
Subscribe returns `None`. Success admits the subscription; a later disconnect or
|
|
413
|
+
channel failure can already have changed its current readiness when the caller
|
|
414
|
+
resumes.
|
|
415
|
+
|
|
416
|
+
Hooks must be short, nonblocking and return `None`. Do not await, perform I/O,
|
|
417
|
+
wait on locks, defer installation, or change task/client lifecycle inside them.
|
|
418
|
+
Async callables, awaitable results and other non-`None` results fail admission.
|
|
419
|
+
The join timeout bounds reply waiting and cannot preempt synchronous code. Hook
|
|
420
|
+
exceptions (including directly raised `CancelledError`) fail with `PHXTopicError`
|
|
421
|
+
and preserve their cause; arbitrary prior hook effects are not rolled back.
|
|
422
|
+
Initial failure belongs to the caller. An established subscription whose installer
|
|
423
|
+
fails is removed and reported once through `on_topic_lost`.
|
|
424
|
+
|
|
425
|
+
Initial cancellation, timeout and leave remove the exact subscription. Physical
|
|
426
|
+
cleanup remains owned if its waiter is canceled again, and shutdown awaits it.
|
|
427
|
+
Never-established subscriptions are not adopted by automatic recovery. Recorded
|
|
428
|
+
queue loss before a hook-enabled installation fails the attempt transiently;
|
|
429
|
+
steady-state queues still drop their oldest message at capacity. There is no
|
|
430
|
+
replay guarantee. A fresh client must install a fresh snapshot after process loss.
|
|
431
|
+
|
|
432
|
+
Join replies match request refs on both protocols. V2 also filters stale channel
|
|
433
|
+
generations by `join_ref`. V1 cannot reliably fence unsolicited events/lifecycle
|
|
434
|
+
frames from older channels; it does not provide V2 event-generation isolation.
|
|
435
|
+
|
|
436
|
+
### Optional reconnect participation
|
|
437
|
+
|
|
438
|
+
`subscribe_to_topic(topic, async_callback=None, *, on_join_response=None,
|
|
439
|
+
blocks_reconnect=True) -> None` preserves required recovery by default. Required
|
|
440
|
+
topics reconcile their first socket-rejoin attempt sequentially before
|
|
441
|
+
`on_reconnect`; transient failures continue through existing recovery/backoff.
|
|
442
|
+
The barrier does not wait for eventual success of every required topic.
|
|
443
|
+
|
|
444
|
+
Set `blocks_reconnect=False` for optional subscriptions. After required first
|
|
445
|
+
attempts, their existing per-topic recovery tasks perform an immediate join and
|
|
446
|
+
retry transient failures with the existing backoff. Required event delivery and
|
|
447
|
+
`on_reconnect` do not wait for optional replies, old callback drains or retries.
|
|
448
|
+
This option cannot isolate synchronous code that blocks the event loop. Explicit
|
|
449
|
+
initial subscribe always waits for its own join response and installation.
|
|
450
|
+
|
|
451
|
+
`on_reconnect` conveys no optional readiness or registration-absence guarantee.
|
|
452
|
+
Check `is_topic_joined()` for current readiness and use `on_topic_lost` to replace
|
|
453
|
+
permanently rejected optional subscriptions; do not blindly resubscribe them
|
|
454
|
+
from `on_reconnect`. Leave, shutdown and another socket disconnect cancel/await
|
|
455
|
+
the owned recovery; obsolete generations cannot install or revive a replacement.
|
|
@@ -13,18 +13,20 @@ from phoenix_channels_python_client.client import (
|
|
|
13
13
|
ReconnectPolicy,
|
|
14
14
|
TopicLostCallback,
|
|
15
15
|
)
|
|
16
|
+
from phoenix_channels_python_client.client_types import JoinResponseCallback
|
|
16
17
|
from phoenix_channels_python_client.protocol_handler import (
|
|
17
18
|
PhoenixChannelsProtocolVersion,
|
|
18
19
|
PHXProtocolHandler,
|
|
19
20
|
)
|
|
20
21
|
from phoenix_channels_python_client.utils import setup_logging
|
|
21
22
|
|
|
22
|
-
__version__ = "0.4.
|
|
23
|
+
__version__ = "0.4.2"
|
|
23
24
|
__author__ = "Phoenix Channels Python Client"
|
|
24
25
|
|
|
25
26
|
__all__ = [
|
|
26
27
|
"DisconnectCallback",
|
|
27
28
|
"HeartbeatAckCallback",
|
|
29
|
+
"JoinResponseCallback",
|
|
28
30
|
"PHXChannelsClient",
|
|
29
31
|
"PHXProtocolHandler",
|
|
30
32
|
"PhoenixChannelsProtocolVersion",
|
|
@@ -198,6 +198,7 @@ class PHXChannelsClient(SupervisorMixin, TopicRuntimeMixin, ReconnectControllerM
|
|
|
198
198
|
self._on_heartbeat_ack = on_heartbeat_ack
|
|
199
199
|
self._on_topic_lost = on_topic_lost
|
|
200
200
|
self._lifecycle_tasks: set[asyncio.Task[None]] = set()
|
|
201
|
+
self._cleanup_tasks: set[asyncio.Task[None]] = set()
|
|
201
202
|
self._forced_close_pending = False
|
|
202
203
|
|
|
203
204
|
def redact_diagnostic(self, text: str) -> str:
|
|
@@ -215,6 +216,7 @@ class PHXChannelsClient(SupervisorMixin, TopicRuntimeMixin, ReconnectControllerM
|
|
|
215
216
|
self._state == ClientState.CLOSED
|
|
216
217
|
and not self._topic_subscriptions
|
|
217
218
|
and self.connection is None
|
|
219
|
+
and not self._cleanup_tasks
|
|
218
220
|
)
|
|
219
221
|
|
|
220
222
|
async def __aenter__(self) -> Self:
|
|
@@ -273,6 +275,8 @@ class PHXChannelsClient(SupervisorMixin, TopicRuntimeMixin, ReconnectControllerM
|
|
|
273
275
|
await self._unsubscribe_all()
|
|
274
276
|
# Includes on_topic_lost callbacks still running for topics already gone.
|
|
275
277
|
await cancel_and_wait(*self._lifecycle_tasks)
|
|
278
|
+
if self._cleanup_tasks:
|
|
279
|
+
await asyncio.gather(*self._cleanup_tasks)
|
|
276
280
|
await self._stop_supervisor()
|
|
277
281
|
await self._cleanup_connection()
|
|
278
282
|
self._connected_event.clear()
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from enum import StrEnum
|
|
5
|
-
from typing import Self
|
|
6
|
+
from typing import Any, Self
|
|
6
7
|
|
|
7
8
|
from pydantic import (
|
|
8
9
|
BaseModel,
|
|
@@ -15,6 +16,8 @@ from pydantic import (
|
|
|
15
16
|
|
|
16
17
|
from phoenix_channels_python_client.exceptions import PHXConnectionError
|
|
17
18
|
|
|
19
|
+
JoinResponseCallback = Callable[[Any], None]
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class ClientState(StrEnum):
|
|
20
23
|
CONNECTING = "connecting"
|