Skip to content

Commit 4c03ecd

Browse files
Sebastian Molendapubnub-release-bot
andauthored
Presence engine - states and events (#178)
* Presence engine - states and events * Fixes and effects * Add missing presence_enable switch * Add ee param to heartbeat, subscribe and leave endpoints * Presence engine, refactors and everything else including meaning of life * Add pnpres to channel groups * rename delayed effects * Removed code repetitions + improved behave tests * Fixed Heartbeat Retry * Fixes * Rename effects to invocations * Add support for subscription state * Fix leaving channels * Added example * Fixes in example * PubNub SDK v7.4.0 release. --------- Co-authored-by: PubNub Release Bot <[email protected]>
1 parent 4ac2f92 commit 4c03ecd

32 files changed

+1694
-622
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ jobs:
7878
mkdir tests/acceptance/encryption/assets/
7979
cp sdk-specifications/features/encryption/assets/* tests/acceptance/encryption/assets/
8080
cp sdk-specifications/features/subscribe/event-engine/happy-path.feature tests/acceptance/subscribe/happy-path.feature
81+
cp sdk-specifications/features/presence/event-engine/presence-engine.feature tests/acceptance/subscribe/presence-engine.feature
8182
8283
sudo pip3 install -r requirements-dev.txt
8384
behave --junit tests/acceptance/pam

.pubnub.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: python
2-
version: 7.3.2
2+
version: 7.4.0
33
schema: 1
44
scm: github.com/pubnub/python
55
sdks:
@@ -18,7 +18,7 @@ sdks:
1818
distributions:
1919
- distribution-type: library
2020
distribution-repository: package
21-
package-name: pubnub-7.3.2
21+
package-name: pubnub-7.4.0
2222
location: https://pypi.org/project/pubnub/
2323
supported-platforms:
2424
supported-operating-systems:
@@ -97,8 +97,8 @@ sdks:
9797
-
9898
distribution-type: library
9999
distribution-repository: git release
100-
package-name: pubnub-7.3.2
101-
location: https://github.com/pubnub/python/releases/download/v7.3.2/pubnub-7.3.2.tar.gz
100+
package-name: pubnub-7.4.0
101+
location: https://github.com/pubnub/python/releases/download/v7.4.0/pubnub-7.4.0.tar.gz
102102
supported-platforms:
103103
supported-operating-systems:
104104
Linux:
@@ -169,6 +169,11 @@ sdks:
169169
license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt
170170
is-required: Required
171171
changelog:
172+
- date: 2024-02-08
173+
version: v7.4.0
174+
changes:
175+
- type: feature
176+
text: "Optional Event Engine for Subscribe Loop."
172177
- date: 2023-11-27
173178
version: v7.3.2
174179
changes:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v7.4.0
2+
February 08 2024
3+
4+
#### Added
5+
- Optional Event Engine for Subscribe Loop.
6+
17
## v7.3.2
28
November 27 2023
39

examples/cli_chat.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import argparse
2+
import asyncio
3+
4+
from os import getenv
5+
from pubnub.callbacks import SubscribeCallback
6+
from pubnub.pubnub_asyncio import EventEngineSubscriptionManager, PubNubAsyncio
7+
from pubnub.pnconfiguration import PNConfiguration
8+
9+
parser = argparse.ArgumentParser(description="Chat with others using PubNub")
10+
parser.add_argument("-n", metavar="name", help="Your name", default=None, required=False)
11+
parser.add_argument("-c", metavar="channel", help="The channel you want to join", default=None, required=False)
12+
args = parser.parse_args()
13+
14+
15+
class ExampleCallback(SubscribeCallback):
16+
def message(self, pubnub, message):
17+
print(f"{message.publisher}> {message.message}\n")
18+
19+
def presence(self, pubnub, presence):
20+
print(f"-- {presence.uuid} {'joined' if presence.event == 'join' else 'left'} \n")
21+
22+
def status(self, pubnub, status):
23+
if status.is_error():
24+
print(f"! Error: {status.error_data}")
25+
else:
26+
print(f"* Status: {status.category.name}")
27+
28+
29+
async def async_input():
30+
print()
31+
await asyncio.sleep(0.1)
32+
return (await asyncio.get_event_loop().run_in_executor(None, input))
33+
34+
35+
async def main():
36+
name = args.name if hasattr(args, "name") else input("Enter your name: ")
37+
channel = args.channel if hasattr(args, "channel") else input("Enter the channel you want to join: ")
38+
39+
print("Welcome to the chat room. Type 'exit' to leave the chat.")
40+
41+
config = PNConfiguration()
42+
config.subscribe_key = getenv("PN_KEY_SUBSCRIBE")
43+
config.publish_key = getenv("PN_KEY_PUBLISH")
44+
config.uuid = name
45+
46+
pubnub = PubNubAsyncio(config, subscription_manager=EventEngineSubscriptionManager)
47+
pubnub.add_listener(ExampleCallback())
48+
49+
pubnub.subscribe().channels(channel).with_presence().execute()
50+
51+
while True:
52+
message = await async_input()
53+
print("\x1b[2K")
54+
if message == "exit":
55+
print("Goodbye!")
56+
break
57+
58+
await pubnub.publish().channel(channel).message(message).future()
59+
60+
61+
if __name__ == '__main__':
62+
loop = asyncio.get_event_loop()
63+
loop.run_until_complete(main())

pubnub/dtos.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ def __init__(self, channels=None, channel_groups=None, presence_enabled=None, ti
1010
self.presence_enabled = presence_enabled
1111
self.timetoken = timetoken
1212

13+
@property
14+
def channels_with_pressence(self):
15+
if not self.presence_enabled:
16+
return self.channels
17+
return self.channels + [ch + '-pnpres' for ch in self.channels]
18+
19+
@property
20+
def groups_with_pressence(self):
21+
if not self.presence_enabled:
22+
return self.channel_groups
23+
return self.channel_groups + [ch + '-pnpres' for ch in self.channel_groups]
24+
1325

1426
class UnsubscribeOperation(object):
1527
def __init__(self, channels=None, channel_groups=None):
@@ -19,6 +31,18 @@ def __init__(self, channels=None, channel_groups=None):
1931
self.channels = channels
2032
self.channel_groups = channel_groups
2133

34+
def get_subscribed_channels(self, channels, with_presence=False) -> list:
35+
result = [ch for ch in channels if ch not in self.channels and not ch.endswith('-pnpres')]
36+
if not with_presence:
37+
return result
38+
return result + [ch + '-pnpres' for ch in result]
39+
40+
def get_subscribed_channel_groups(self, channel_groups, with_presence=False) -> list:
41+
result = [grp for grp in channel_groups if grp not in self.channel_groups and not grp.endswith('-pnpres')]
42+
if not with_presence:
43+
return result
44+
return result + [grp + '-pnpres' for grp in result]
45+
2246

2347
class StateOperation(object):
2448
def __init__(self, channels=None, channel_groups=None, state=None):

pubnub/endpoints/presence/heartbeat.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def custom_params(self):
5252
if self._state is not None and len(self._state) > 0:
5353
params['state'] = utils.url_write(self._state)
5454

55+
if hasattr(self.pubnub, '_subscription_manager'):
56+
params.update(self.pubnub._subscription_manager.get_custom_params())
57+
5558
return params
5659

5760
def create_response(self, envelope):

pubnub/endpoints/presence/leave.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def custom_params(self):
3636
if len(self._groups) > 0:
3737
params['channel-group'] = utils.join_items(self._groups)
3838

39+
if hasattr(self.pubnub, '_subscription_manager'):
40+
params.update(self.pubnub._subscription_manager.get_custom_params())
41+
3942
return params
4043

4144
def build_path(self):

pubnub/endpoints/pubsub/subscribe.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def __init__(self, pubnub):
1818
self._filter_expression = None
1919
self._timetoken = None
2020
self._with_presence = None
21+
self._state = None
2122

2223
def channels(self, channels):
2324
utils.extend_list(self._channels, channels)
@@ -44,6 +45,10 @@ def region(self, region):
4445

4546
return self
4647

48+
def state(self, state):
49+
self._state = state
50+
return self
51+
4752
def http_method(self):
4853
return HttpMethod.GET
4954

@@ -75,6 +80,12 @@ def custom_params(self):
7580
if not self.pubnub.config.heartbeat_default_values:
7681
params['heartbeat'] = self.pubnub.config.presence_timeout
7782

83+
if self._state is not None and len(self._state) > 0:
84+
params['state'] = utils.url_write(self._state)
85+
86+
if hasattr(self.pubnub, '_subscription_manager'):
87+
params.update(self.pubnub._subscription_manager.get_custom_params())
88+
7889
return params
7990

8091
def create_response(self, envelope):

pubnub/enums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def string(cls, method):
1919
return "PATCH"
2020

2121

22-
class PNStatusCategory(object):
22+
class PNStatusCategory(Enum):
2323
PNUnknownCategory = 1
2424
PNAcknowledgmentCategory = 2
2525
PNAccessDeniedCategory = 3

pubnub/event_engine/containers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class PresenceStateContainer:
2+
channel_states: dict
3+
4+
def __init__(self):
5+
self.channel_states = {}
6+
7+
def register_state(self, state: dict, channels: list):
8+
for channel in channels:
9+
self.channel_states[channel] = state
10+
11+
def get_state(self, channels: list):
12+
return {channel: self.channel_states[channel] for channel in channels if channel in self.channel_states}
13+
14+
def get_channels_states(self, channels: list):
15+
return {channel: self.channel_states[channel] for channel in channels if channel in self.channel_states}

0 commit comments

Comments
 (0)