Skip to content

Commit 039f27e

Browse files
Merge pull request ably#276 from ably/feature/channelLifecycleStatus
Add support to get channel lifecycle status
2 parents 4862db0 + 982e18a commit 039f27e

4 files changed

Lines changed: 182 additions & 0 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ presence_page.items
124124
presence_page.items[0].client_id # client_id of first member
125125
```
126126

127+
### Getting the channel status
128+
129+
```python
130+
channel_status = await channel.status() # Returns a ChannelDetails object
131+
channel_status.channel_id # Channel identifier
132+
channel_status.status # ChannelStatus object
133+
channel_status.status.occupancy # ChannelOccupancy object
134+
channel_status.status.occupancy.metrics # ChannelMetrics object
135+
```
136+
127137
### Symmetric end-to-end encrypted payloads on a channel
128138

129139
When a 128 bit or 256 bit key is provided to the library, all payloads are encrypted and decrypted automatically using that key on the channel. The secret key is never transmitted to Ably and thus it is the developer's responsibility to distribute a secret key to both publishers and subscribers.

ably/rest/channel.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import msgpack
1111

1212
from ably.http.paginatedresult import PaginatedResult, format_params
13+
from ably.types.channeldetails import ChannelDetails
1314
from ably.types.message import Message, make_message_response_handler
1415
from ably.types.presence import Presence
1516
from ably.util.crypto import get_cipher
@@ -137,6 +138,14 @@ async def publish(self, *args, **kwargs):
137138

138139
return await self._publish(*args, **kwargs)
139140

141+
async def status(self):
142+
"""Retrieves current channel active status with no. of publishers, subscribers, presence_members etc"""
143+
144+
path = '/channels/%s' % self.name
145+
response = await self.ably.http.get(path)
146+
obj = response.to_native()
147+
return ChannelDetails.from_dict(obj)
148+
140149
@property
141150
def ably(self):
142151
return self.__ably

ably/types/channeldetails.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from __future__ import annotations
2+
3+
4+
class ChannelDetails:
5+
6+
def __init__(self, channel_id, status):
7+
self.__channel_id = channel_id
8+
self.__status = status
9+
10+
@property
11+
def channel_id(self) -> str:
12+
return self.__channel_id
13+
14+
@property
15+
def status(self) -> ChannelStatus:
16+
return self.__status
17+
18+
@staticmethod
19+
def from_dict(obj):
20+
kwargs = {
21+
'channel_id': obj.get("channelId"),
22+
'status': ChannelStatus.from_dict(obj.get("status"))
23+
}
24+
25+
return ChannelDetails(**kwargs)
26+
27+
28+
class ChannelStatus:
29+
30+
def __init__(self, is_active, occupancy):
31+
self.__is_active = is_active
32+
self.__occupancy = occupancy
33+
34+
@property
35+
def is_active(self) -> bool:
36+
return self.__is_active
37+
38+
@property
39+
def occupancy(self) -> ChannelOccupancy:
40+
return self.__occupancy
41+
42+
@staticmethod
43+
def from_dict(obj):
44+
kwargs = {
45+
'is_active': obj.get("isActive"),
46+
'occupancy': ChannelOccupancy.from_dict(obj.get("occupancy"))
47+
}
48+
49+
return ChannelStatus(**kwargs)
50+
51+
52+
class ChannelOccupancy:
53+
54+
def __init__(self, metrics):
55+
self.__metrics = metrics
56+
57+
@property
58+
def metrics(self) -> ChannelMetrics:
59+
return self.__metrics
60+
61+
@staticmethod
62+
def from_dict(obj):
63+
kwargs = {
64+
'metrics': ChannelMetrics.from_dict(obj.get("metrics"))
65+
}
66+
67+
return ChannelOccupancy(**kwargs)
68+
69+
70+
class ChannelMetrics:
71+
72+
def __init__(self, connections, presence_connections, presence_members,
73+
presence_subscribers, publishers, subscribers):
74+
self.__connections = connections
75+
self.__presence_connections = presence_connections
76+
self.__presence_members = presence_members
77+
self.__presence_subscribers = presence_subscribers
78+
self.__publishers = publishers
79+
self.__subscribers = subscribers
80+
81+
@property
82+
def connections(self) -> int:
83+
return self.__connections
84+
85+
@property
86+
def presence_connections(self) -> int:
87+
return self.__presence_connections
88+
89+
@property
90+
def presence_members(self) -> int:
91+
return self.__presence_members
92+
93+
@property
94+
def presence_subscribers(self) -> int:
95+
return self.__presence_subscribers
96+
97+
@property
98+
def publishers(self) -> int:
99+
return self.__publishers
100+
101+
@property
102+
def subscribers(self) -> int:
103+
return self.__subscribers
104+
105+
@staticmethod
106+
def from_dict(obj):
107+
kwargs = {
108+
'connections': obj.get("connections"),
109+
'presence_connections': obj.get("presenceConnections"),
110+
'presence_members': obj.get("presenceMembers"),
111+
'presence_subscribers': obj.get("presenceSubscribers"),
112+
'publishers': obj.get("publishers"),
113+
'subscribers': obj.get("subscribers")
114+
}
115+
116+
return ChannelMetrics(**kwargs)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import logging
2+
3+
from test.ably.restsetup import RestSetup
4+
from test.ably.utils import VaryByProtocolTestsMetaclass, BaseAsyncTestCase
5+
6+
log = logging.getLogger(__name__)
7+
8+
9+
class TestRestChannelStatus(BaseAsyncTestCase, metaclass=VaryByProtocolTestsMetaclass):
10+
11+
async def setUp(self):
12+
self.ably = await RestSetup.get_ably_rest()
13+
14+
async def tearDown(self):
15+
await self.ably.close()
16+
17+
def per_protocol_setup(self, use_binary_protocol):
18+
self.ably.options.use_binary_protocol = use_binary_protocol
19+
self.use_binary_protocol = use_binary_protocol
20+
21+
async def test_channel_status(self):
22+
channel_name = self.get_channel_name('test_channel_status')
23+
channel = self.ably.channels[channel_name]
24+
25+
channel_status = await channel.status()
26+
27+
assert channel_status is not None, "Expected non-None channel_status"
28+
assert channel_name == channel_status.channel_id, "Expected channel name to match"
29+
assert channel_status.status.is_active is True, "Expected is_active to be True"
30+
assert isinstance(channel_status.status.occupancy.metrics.publishers, int) and\
31+
channel_status.status.occupancy.metrics.publishers >= 0,\
32+
"Expected publishers to be a non-negative int"
33+
assert isinstance(channel_status.status.occupancy.metrics.connections, int) and\
34+
channel_status.status.occupancy.metrics.connections >= 0,\
35+
"Expected connections to be a non-negative int"
36+
assert isinstance(channel_status.status.occupancy.metrics.subscribers, int) and\
37+
channel_status.status.occupancy.metrics.subscribers >= 0,\
38+
"Expected subscribers to be a non-negative int"
39+
assert isinstance(channel_status.status.occupancy.metrics.presence_members, int) and\
40+
channel_status.status.occupancy.metrics.presence_members >= 0,\
41+
"Expected presence_members to be a non-negative int"
42+
assert isinstance(channel_status.status.occupancy.metrics.presence_connections, int) and\
43+
channel_status.status.occupancy.metrics.presence_connections >= 0,\
44+
"Expected presence_connections to be a non-negative int"
45+
assert isinstance(channel_status.status.occupancy.metrics.presence_subscribers, int) and\
46+
channel_status.status.occupancy.metrics.presence_subscribers >= 0,\
47+
"Expected presence_subscribers to be a non-negative int"

0 commit comments

Comments
 (0)