From cf8cbed9df87a59bbd6ded5a214d3bfba4804b8e Mon Sep 17 00:00:00 2001 From: Sebastian Molenda Date: Thu, 9 May 2024 17:35:24 +0200 Subject: [PATCH] Feat/listeners (#186) * PubNub Entrypoint * Fixes from tests. * Listeners * Refactor names, add SubscriptionSet, remove debug * Event engine as a default subscription manager * Update runner * move tt, tr and with_presence to subscribe method * Rework subscriptionSet to use PubNubSubscriptions * remove type from subscriptionset * Fixed subscription set and examples * Add subscription set and subscription item level listeners * Fix in example * PubNub SDK v8.0.0 release. --------- Co-authored-by: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> --- .github/workflows/run-tests.yml | 12 +- .pubnub.yml | 15 +- CHANGELOG.md | 7 + examples/subscription_object.py | 122 +++++++ examples/subscription_object_threads.py | 200 +++++++++++ examples/subscription_set.py | 97 +++++ pubnub/builders.py | 34 +- pubnub/dtos.py | 32 +- .../objects_v2/channel/set_channel.py | 2 + pubnub/event_engine/effects.py | 9 +- pubnub/event_engine/models/states.py | 6 +- pubnub/models/consumer/pubsub.py | 5 +- pubnub/models/subscription.py | 332 ++++++++++++++++++ pubnub/pubnub.py | 2 +- pubnub/pubnub_asyncio.py | 23 +- pubnub/pubnub_core.py | 30 +- pubnub/request_handlers/requests_handler.py | 2 +- pubnub/workers.py | 4 +- setup.py | 2 +- tests/helper.py | 4 +- tests/integrational/asyncio/test_heartbeat.py | 28 +- tests/integrational/asyncio/test_subscribe.py | 36 +- ...nd_download_encrypted_file_cipher_key.json | 54 +-- .../native_threads/test_here_now.py | 3 - .../native_threads/test_subscribe.py | 57 +-- tests/integrational/vcr_asyncio_sleeper.py | 2 + 26 files changed, 961 insertions(+), 159 deletions(-) create mode 100644 examples/subscription_object.py create mode 100644 examples/subscription_object_threads.py create mode 100644 examples/subscription_set.py create mode 100644 pubnub/models/subscription.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 189cf343..877292e5 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,12 +10,12 @@ defaults: run: shell: bash env: - PN_KEY_PUBLISH: ${{ secrets.PN_KEY_PUBLISH }} - PN_KEY_SUBSCRIBE: ${{ secrets.PN_KEY_SUBSCRIBE }} - PN_KEY_SECRET: ${{ secrets.PN_KEY_SECRET }} - PN_KEY_PAM_PUBLISH: ${{ secrets.PN_KEY_PAM_PUBLISH }} - PN_KEY_PAM_SUBSCRIBE: ${{ secrets.PN_KEY_PAM_SUBSCRIBE }} - PN_KEY_PAM_SECRET: ${{ secrets.PN_KEY_PAM_SECRET }} + PN_KEY_PUBLISH: ${{ secrets.SDK_PUB_KEY }} + PN_KEY_SUBSCRIBE: ${{ secrets.SDK_SUB_KEY }} + PN_KEY_SECRET: ${{ secrets.SDK_SEC_KEY }} + PN_KEY_PAM_PUBLISH: ${{ secrets.SDK_PAM_PUB_KEY }} + PN_KEY_PAM_SUBSCRIBE: ${{ secrets.SDK_PAM_SUB_KEY }} + PN_KEY_PAM_SECRET: ${{ secrets.SDK_PAM_SEC_KEY }} jobs: tests: diff --git a/.pubnub.yml b/.pubnub.yml index efe7712e..bc035c3c 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,5 +1,5 @@ name: python -version: 7.4.4 +version: 8.0.0 schema: 1 scm: github.com/pubnub/python sdks: @@ -18,7 +18,7 @@ sdks: distributions: - distribution-type: library distribution-repository: package - package-name: pubnub-7.4.4 + package-name: pubnub-8.0.0 location: https://pypi.org/project/pubnub/ supported-platforms: supported-operating-systems: @@ -97,8 +97,8 @@ sdks: - distribution-type: library distribution-repository: git release - package-name: pubnub-7.4.4 - location: https://github.com/pubnub/python/releases/download/v7.4.4/pubnub-7.4.4.tar.gz + package-name: pubnub-8.0.0 + location: https://github.com/pubnub/python/releases/download/v8.0.0/pubnub-8.0.0.tar.gz supported-platforms: supported-operating-systems: Linux: @@ -169,6 +169,13 @@ sdks: license-url: https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt is-required: Required changelog: + - date: 2024-05-09 + version: v8.0.0 + changes: + - type: feature + text: "A new version of subscription and presence handling is enabled by default (enableEventEngine flag is set to true). Please consult the documentation for new PNStatus values that are emitted for subscriptions, as code changes might be required to support this change." + - type: feature + text: "Channels, ChannelGroups, ChannelMetadata and UserMetadata." - date: 2024-04-10 version: v7.4.4 changes: diff --git a/CHANGELOG.md b/CHANGELOG.md index 374ff02c..49b10d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v8.0.0 +May 09 2024 + +#### Added +- A new version of subscription and presence handling is enabled by default (enableEventEngine flag is set to true). Please consult the documentation for new PNStatus values that are emitted for subscriptions, as code changes might be required to support this change. +- Channels, ChannelGroups, ChannelMetadata and UserMetadata. + ## v7.4.4 April 10 2024 diff --git a/examples/subscription_object.py b/examples/subscription_object.py new file mode 100644 index 00000000..9cf0d790 --- /dev/null +++ b/examples/subscription_object.py @@ -0,0 +1,122 @@ +import time + +from os import getenv +from pubnub.callbacks import SubscribeCallback +from pubnub.pnconfiguration import PNConfiguration +from pubnub.pubnub import PubNub + + +# Listeners declaration +def on_message(listener): + def message_callback(message): + print(f"\033[94mMessage received on: {listener}: \n{message.message}\033[0m\n") + return message_callback + + +def on_message_action(listener): + def message_callback(message_action): + print(f"\033[5mMessageAction received on: {listener}: \n{message_action.value}\033[0m\n") + return message_callback + + +def on_presence(listener): + def presence_callback(presence): + print(f"\033[0;32mPresence received on: {listener}: \t{presence.uuid} {presence.event}s " + f"{presence.subscription or presence.channel}\033[0m") + return presence_callback + + +def on_status(listener): + def status_callback(status): + print(f"\033[92mStatus received on: {listener}: \t{status.category.name}\033[0m") + return status_callback + + +def on_signal(listener): + def signal_callback(signal): + print(f"\033[0;36mSignal received on: {listener}: \n{signal.publisher} says: \t{signal.message}\033[0m") + return signal_callback + + +def on_channel_metadata(listener): + def channel_metadata_callback(channel_meta): + print(f"\033[0;36mChannel metadata received on: {listener}: \n{channel_meta.__dict__}\033[0m") + return channel_metadata_callback + + +class PrintListener(SubscribeCallback): + def status(self, _, status): + print(f'\033[92mPrintListener.status:\n{status.category.name}\033[0m') + + def message(self, _, message): + print(f'\033[94mPrintListener.message:\n{message.message}\033[0m') + + def presence(self, _, presence): + print(f'PrintListener.presence:\n{presence.uuid} {presence.event}s ' + f'{presence.subscription or presence.channel}\033[0m') + + def signal(self, _, signal): + print(f'PrintListener.signal:\n{signal.message} from {signal.publisher}\033[0m') + + def channel(self, _, channel): + print(f'\033[0;37mChannel Meta:\n{channel.__dict__}\033[0m') + + def uuid(self, _, uuid): + print(f'User Meta:\n{uuid.__dict__}\033[0m') + + def membership(self, _, membership): + print(f'Membership:\n{membership.__dict__}\033[0m') + + def message_action(self, _, message_action): + print(f'PrintListener.message_action {message_action}\033[0m') + + def file(self, _, file_message): + print(f' {file_message.__dict__}\033[0m') + + +channel = 'test' +group_name = 'test-group' + +config = PNConfiguration() +config.subscribe_key = getenv("PN_KEY_SUBSCRIBE") +config.publish_key = getenv("PN_KEY_PUBLISH") +config.user_id = "example" +config.enable_subscribe = True +config.daemon = True + +pubnub = PubNub(config) +pubnub.add_listener(PrintListener()) + +# Subscribing + +# Channel test, no presence, first channel object +print('Creating channel object for "test"') +test1 = pubnub.channel(f'{channel}') +print('Creating subscription object for "test"') +t1_subscription = test1.subscription(with_presence=True) +t1_subscription.on_message = on_message('listener_1') +t1_subscription.on_message_action = on_message_action('listener_1') +t1_subscription.on_presence = on_presence('listener_1') +t1_subscription.on_status = on_status('listener_1') +t1_subscription.on_signal = on_signal('listener_1') + +print('We\'re not yet subscribed to channel "test". So let\'s do it now.') +t1_subscription.subscribe() +print("Now we're subscribed. We should receive status: connected") + +# Testing message delivery +publish_result = pubnub.publish() \ + .channel(f'{channel}') \ + .message('Hello channel "test" from PubNub Python SDK') \ + .meta({'lang': 'en'}) \ + .sync() + +time.sleep(2) + +print('Removing subscription object for "test"') +t1_subscription.unsubscribe() +time.sleep(2) + +print('Exiting') +pubnub.stop() +exit(0) diff --git a/examples/subscription_object_threads.py b/examples/subscription_object_threads.py new file mode 100644 index 00000000..a85b10b7 --- /dev/null +++ b/examples/subscription_object_threads.py @@ -0,0 +1,200 @@ +import time + +from os import getenv +from pubnub.callbacks import SubscribeCallback +from pubnub.pnconfiguration import PNConfiguration +from pubnub.pubnub import PubNub + + +# Listeners declaration +def on_message(listener): + def message_callback(message): + print(f"\033[94mMessage received on: {listener}: \n{message.message}\033[0m\n") + return message_callback + + +def on_message_action(listener): + def message_callback(message_action): + print(f"\033[5mMessageAction received on: {listener}: \n{message_action.value}\033[0m\n") + return message_callback + + +def on_presence(listener): + def presence_callback(presence): + print(f"\033[0;32mPresence received on: {listener}: \t{presence.uuid} {presence.event}s " + f"{presence.subscription or presence.channel}\033[0m") + return presence_callback + + +def on_status(listener): + def status_callback(status): + print(f"\033[92mStatus received on: {listener}: \t{status.category.name}\033[0m") + return status_callback + + +def on_signal(listener): + def signal_callback(signal): + print(f"\033[0;36mSignal received on: {listener}: \n{signal.publisher} says: \t{signal.message}\033[0m") + return signal_callback + + +def on_channel_metadata(listener): + def channel_metadata_callback(channel_meta): + print(f"\033[0;36mChannel metadata received on: {listener}: \n{channel_meta.__dict__}\033[0m") + return channel_metadata_callback + + +class PrintListener(SubscribeCallback): + def status(self, _, status): + print(f'\033[92mPrintListener.status:\n{status.category.name}\033[0m') + + def message(self, _, message): + print(f'\033[94mPrintListener.message:\n{message.message}\033[0m') + + def presence(self, _, presence): + print(f'PrintListener.presence:\n{presence.uuid} {presence.event}s ' + f'{presence.subscription or presence.channel}\033[0m') + + def signal(self, _, signal): + print(f'PrintListener.signal:\n{signal.message} from {signal.publisher}\033[0m') + + def channel(self, _, channel): + print(f'\033[0;37mChannel Meta:\n{channel.__dict__}\033[0m') + + def uuid(self, _, uuid): + print(f'User Meta:\n{uuid.__dict__}\033[0m') + + def membership(self, _, membership): + print(f'Membership:\n{membership.__dict__}\033[0m') + + def message_action(self, _, message_action): + print(f'PrintListener.message_action {message_action}\033[0m') + + def file(self, _, file_message): + print(f' {file_message.__dict__}\033[0m') + + +channel = 'test' +group_name = 'test-group' + +config = PNConfiguration() +config.subscribe_key = getenv("PN_KEY_SUBSCRIBE") +config.publish_key = getenv("PN_KEY_PUBLISH") +config.user_id = "example" +config.enable_subscribe = True +config.daemon = True + +pubnub = PubNub(config) +pubnub.add_listener(PrintListener()) + +# Subscribing + +# Channel test, no presence, first channel object +print('Creating channel object for "test"') +test1 = pubnub.channel(f'{channel}') +print('Creating subscription object for "test"') +t1_subscription = test1.subscription(with_presence=False) +t1_subscription.on_message = on_message('listener_1') +t1_subscription.on_message_action = on_message_action('listener_1') +t1_subscription.on_presence = on_presence('listener_1') +t1_subscription.on_status = on_status('listener_1') +t1_subscription.on_signal = on_signal('listener_1') + +print('We\'re not yet subscribed to channel "test". So let\'s do it now.') +t1_subscription.subscribe() +print("Now we're subscribed. We should receive status: connected") + +time.sleep(3) +print("We don't see any presence event since we don't have it enabled yet") + +print('Creating second subscription object for channel "test.2"') +test2 = pubnub.channel(f'{channel}.2') +print('Creating subscription object for "test"') +t2_subscription = test1.subscription(with_presence=True) + +t2_subscription.on_message = on_message('listener_2') +t2_subscription.on_presence = on_presence('listener_2') +t2_subscription.on_status = on_status('listener_2') +t2_subscription.on_signal = on_signal('listener_2') +t2_subscription.subscribe() + +print('Now we\'re subscribed to "test" with two listeners. one with presence and one without') +print('So we should see presence events only for listener "test2" for channel "test2"') +time.sleep(2) + +# Channel test3, no presence, third channel object +print('Creating channel object for "test.3"') +test3 = pubnub.channel(f'{channel}.3') +print('Creating subscription object for "test.3"') +t3_subscription = test3.subscription() +t3_subscription.on_message = on_message('listener_3') +t3_subscription.on_presence = on_presence('listener_3') +t3_subscription.on_status = on_status('listener_3') +t3_subscription.on_signal = on_signal('listener_3') +print('We subscribe to third channel so we should see three "connected" statuses and no new presence events') +t3_subscription.subscribe() + +print('Creating wildcard object for "test.*"') +wildcard_channel = pubnub.channel(f'{channel}.*') +print('Creating wildcard subscription object for "test.*"') +wildcard = wildcard_channel.subscription() +wildcard.on_message = on_message('WILDCARD') +wildcard.on_presence = on_presence('WILDCARD') +wildcard.on_status = on_status('WILDCARD') +wildcard.on_signal = on_signal('WILDCARD') +print('We subscribe to all channels "test.*"') +wildcard.subscribe() + +print('Creating Group with "test.2" and "test.3"') +pubnub.add_channel_to_channel_group() \ + .channels(['test']) \ + .channel_group(group_name) \ + .sync() + +print('Creating group object for "test_group"') +group = pubnub.channel_group(f'{group_name}') +print('Creating wildcard subscription object for "group_name"') +group_subscription = group.subscription() +group_subscription.on_message = on_message('group') +group_subscription.on_presence = on_presence('group') +group_subscription.on_status = on_status('group') +group_subscription.on_signal = on_signal('group') +print('We subscribe to the channel group "test_group"') +group_subscription.subscribe() + +print('Now we publish messages to each channel separately') +time.sleep(1) + +# Testing message delivery +publish_result = pubnub.publish() \ + .channel(f'{channel}') \ + .message('Hello channel "test" from PubNub Python SDK') \ + .meta({'lang': 'en'}) \ + .sync() + +pubnub.publish() \ + .channel(f'{channel}.2') \ + .message('Nau mai ki te hongere "test.2" mai i PubNub Python SDK') \ + .meta({'lang': 'mi'}) \ + .sync() + +pubnub.publish() \ + .channel(f'{channel}.3') \ + .message('Bienvenido al canal "test.3" de PubNub Python SDK') \ + .meta({'lang': 'es'}) \ + .sync() + +pubnub.publish() \ + .channel(f'{channel}.4') \ + .message('Ciao canale "test.4" da PubNub Python SDK') \ + .meta({'lang': 'it'}) \ + .sync() + +time.sleep(1) + +print('Removing second subscription object for "test"') +t1_subscription.unsubscribe() + +print('Exiting') +pubnub.stop() +exit(0) diff --git a/examples/subscription_set.py b/examples/subscription_set.py new file mode 100644 index 00000000..8b2f9139 --- /dev/null +++ b/examples/subscription_set.py @@ -0,0 +1,97 @@ +import time + +from os import getenv +from pubnub.callbacks import SubscribeCallback +from pubnub.pnconfiguration import PNConfiguration +from pubnub.pubnub import PubNub + + +# Listeners declaration +def on_message(message): + print(f"\033[94mMessage received on {message.channel}: \n{message.message}\033[0m") + + +def on_presence(presence): + print(f"\033[0;32mPresence event received on: {presence.subscription or presence.channel}: ", + f" \t{presence.uuid} {presence.event}s \033[0m") + + +class PrintListener(SubscribeCallback): + def status(self, _, status): + print(f'\033[1;31mPrintListener.status:\n{status.category.name}\033[0m') + + def presence(self, _, presence): + print(f"\033[0;32mPresence event received on: {presence.subscription or presence.channel}: ", + f" \t{presence.uuid} {presence.event}s \033[0m") + + +channel = 'test' + +config = PNConfiguration() +config.subscribe_key = getenv("PN_KEY_SUBSCRIBE") +config.publish_key = getenv("PN_KEY_PUBLISH") +config.user_id = "example" +config.enable_subscribe = True +config.daemon = True + +pubnub = PubNub(config) +pubnub.add_listener(PrintListener()) + +pubnub.add_channel_to_channel_group().channels(['test', 'test_in_group']).channel_group('group-test').sync() + +# Subscribing +channel_1 = pubnub.channel(channel).subscription() + +channel_2 = pubnub.channel(f'{channel}.2').subscription(with_presence=True) +channel_x = pubnub.channel(f'{channel}.*').subscription(with_presence=True) +channel_x.on_message = lambda message: print(f"\033[96mWildcard {message.channel}: \n{message.message}\033[0m") + +group = pubnub.channel_group('group-test').subscription() +group.on_message = lambda message: print(f"\033[96mChannel Group {message.channel}: \n{message.message}\033[0m") + +subscription_set = pubnub.subscription_set([channel_1, channel_2, channel_x, group]) +subscription_set.on_message = on_message +subscription_set.on_presence = on_presence + +set_subscription = subscription_set.subscribe() + +time.sleep(1) + +# Testing message delivery +publish_result = pubnub.publish() \ + .channel(f'{channel}') \ + .message('Hello channel "test" from PubNub Python SDK') \ + .meta({'lang': 'en'}) \ + .sync() + +time.sleep(1) +publish_result = pubnub.publish() \ + .channel(f'{channel}.2') \ + .message('PubNub Python SDK の Hello チャンネル「test」') \ + .meta({'lang': 'ja'}) \ + .sync() + +time.sleep(1) +publish_result = pubnub.publish() \ + .channel(f'{channel}.3') \ + .message('PubNub Python SDK mówi cześć') \ + .meta({'lang': 'pl'}) \ + .sync() +time.sleep(1) + +time.sleep(1) +publish_result = pubnub.publish() \ + .channel(f'{channel}_in_group') \ + .message('Hola desde el SDK de Python de Pubnub.') \ + .meta({'lang': 'es'}) \ + .sync() +time.sleep(1) + +print('Removing subscription object for "test"') +pubnub.remove_channel_from_channel_group().channels(['test']).channel_group('group-test').sync() +time.sleep(1) + +subscription_set.unsubscribe() +print('Exiting') +pubnub.stop() +exit(0) diff --git a/pubnub/builders.py b/pubnub/builders.py index d4a58e06..03e8d003 100644 --- a/pubnub/builders.py +++ b/pubnub/builders.py @@ -1,13 +1,14 @@ from abc import ABCMeta, abstractmethod -from .dtos import SubscribeOperation, UnsubscribeOperation + +from pubnub.models.subscription import PubNubChannel, PubNubChannelGroup from . import utils class PubSubBuilder(object): __metaclass__ = ABCMeta - def __init__(self, subscription_manager): - self._subscription_manager = subscription_manager + def __init__(self, pubnub_instance): + self._pubnub = pubnub_instance self._channel_subscriptions = [] self._channel_group_subscriptions = [] @@ -28,8 +29,8 @@ def execute(self): class SubscribeBuilder(PubSubBuilder): - def __init__(self, subscription_manager): - super(SubscribeBuilder, self).__init__(subscription_manager) + def __init__(self, pubnub_instance): + super(SubscribeBuilder, self).__init__(pubnub_instance) self._presence_enabled = False self._timetoken = 0 @@ -42,27 +43,20 @@ def with_timetoken(self, timetoken): return self def channel_subscriptions(self): - return self._channel_subscriptions + return [PubNubChannel(self._pubnub, channel).subscription(self._presence_enabled) + for channel in self._channel_subscriptions] def channel_group_subscriptions(self): - return self._channel_group_subscriptions + return [PubNubChannelGroup(self._pubnub, group).subscription(self._presence_enabled) + for group in self._channel_group_subscriptions] def execute(self): - subscribe_operation = SubscribeOperation( - channels=self._channel_subscriptions, - channel_groups=self._channel_group_subscriptions, - timetoken=self._timetoken, - presence_enabled=self._presence_enabled - ) + subscription = self._pubnub.subscription_set(self.channel_subscriptions() + self.channel_group_subscriptions()) - self._subscription_manager.adapt_subscribe_builder(subscribe_operation) + subscription.subscribe(timetoken=self._timetoken) class UnsubscribeBuilder(PubSubBuilder): def execute(self): - unsubscribe_operation = UnsubscribeOperation( - channels=self._channel_subscriptions, - channel_groups=self._channel_group_subscriptions - ) - - self._subscription_manager.adapt_unsubscribe_builder(unsubscribe_operation) + self._pubnub._subscription_registry.unsubscribe(channels=self._channel_subscriptions, + groups=self._channel_group_subscriptions) diff --git a/pubnub/dtos.py b/pubnub/dtos.py index 047714a0..2ceb2d7d 100644 --- a/pubnub/dtos.py +++ b/pubnub/dtos.py @@ -22,6 +22,14 @@ def groups_with_pressence(self): return self.channel_groups return self.channel_groups + [ch + '-pnpres' for ch in self.channel_groups] + @property + def channels_without_presence(self): + return list(filter(lambda ch: not ch.endswith('-pnpres'), self.channels)) + + @property + def channel_groups_without_presence(self): + return list(filter(lambda gr: not gr.endswith('-pnpres'), self.channel_groups)) + class UnsubscribeOperation(object): def __init__(self, channels=None, channel_groups=None): @@ -31,17 +39,19 @@ def __init__(self, channels=None, channel_groups=None): self.channels = channels self.channel_groups = channel_groups - def get_subscribed_channels(self, channels, with_presence=False) -> list: - result = [ch for ch in channels if ch not in self.channels and not ch.endswith('-pnpres')] - if not with_presence: - return result - return result + [ch + '-pnpres' for ch in result] - - def get_subscribed_channel_groups(self, channel_groups, with_presence=False) -> list: - result = [grp for grp in channel_groups if grp not in self.channel_groups and not grp.endswith('-pnpres')] - if not with_presence: - return result - return result + [grp + '-pnpres' for grp in result] + def get_subscribed_channels(self, channels) -> list: + return [ch for ch in channels if ch not in self.channels] + + def get_subscribed_channel_groups(self, channel_groups) -> list: + return [grp for grp in channel_groups if grp not in self.channel_groups] + + @property + def channels_without_presence(self): + return list(filter(lambda ch: not ch.endswith('-pnpres'), self.channels)) + + @property + def channel_groups_without_presence(self): + return list(filter(lambda gr: not gr.endswith('-pnpres'), self.channel_groups)) class StateOperation(object): diff --git a/pubnub/endpoints/objects_v2/channel/set_channel.py b/pubnub/endpoints/objects_v2/channel/set_channel.py index 778dad7c..091ee097 100644 --- a/pubnub/endpoints/objects_v2/channel/set_channel.py +++ b/pubnub/endpoints/objects_v2/channel/set_channel.py @@ -48,6 +48,8 @@ def build_data(self): payload = { "name": self._name, "description": self._description, + "status": self._status, + "type": self._type, "custom": self._custom } payload = StatusTypeAwareEndpoint.build_data(self, payload) diff --git a/pubnub/event_engine/effects.py b/pubnub/event_engine/effects.py index d81fa5ff..04f5b760 100644 --- a/pubnub/event_engine/effects.py +++ b/pubnub/event_engine/effects.py @@ -273,8 +273,9 @@ def get_timetoken(self): class HeartbeatEffect(Effect): def run(self): - channels = self.invocation.channels - groups = self.invocation.groups + channels = list(filter(lambda ch: not ch.endswith('-pnpres'), self.invocation.channels)) + groups = list(filter(lambda gr: not gr.endswith('-pnpres'), self.invocation.groups)) + if hasattr(self.pubnub, 'event_loop'): self.stop_event = self.get_new_stop_event() self.run_async(self.heartbeat(channels=channels, groups=groups, stop_event=self.stop_event)) @@ -362,6 +363,10 @@ async def heartbeat(self, channels, groups, attempt, stop_event): groups=self.invocation.groups, reason=self.invocation.reason, attempt=self.invocation.attempts)) + + channels = list(filter(lambda ch: not ch.endswith('-pnpres'), self.invocation.channels)) + groups = list(filter(lambda gr: not gr.endswith('-pnpres'), self.invocation.groups)) + request = Heartbeat(self.pubnub).channels(channels).channel_groups(groups).cancellation_event(stop_event) delay = self.calculate_reconnection_delay(attempt) self.logger.warning(f'Will retry to Heartbeat in {delay}s') diff --git a/pubnub/event_engine/models/states.py b/pubnub/event_engine/models/states.py index 05190b21..01a489fc 100644 --- a/pubnub/event_engine/models/states.py +++ b/pubnub/event_engine/models/states.py @@ -982,10 +982,12 @@ def joined(self, event: events.HeartbeatJoinedEvent, context: PNContext) -> PNTr def left(self, event: events.HeartbeatLeftEvent, context: PNContext) -> PNTransition: self._context.update(context) for channel in event.channels: - self._context.channels.remove(channel) + if channel in self._context.channels: + self._context.channels.remove(channel) for group in event.groups: - self._context.groups.remove(group) + if group in self._context.groups: + self._context.groups.remove(group) or None invocation = None if not event.suppress_leave: diff --git a/pubnub/models/consumer/pubsub.py b/pubnub/models/consumer/pubsub.py index bf8f2505..047010b5 100644 --- a/pubnub/models/consumer/pubsub.py +++ b/pubnub/models/consumer/pubsub.py @@ -84,9 +84,10 @@ def __init__(self, event, uuid, timestamp, occupancy, subscription, channel, class PNMessageActionResult(PNMessageAction): - - def __init__(self, result): + def __init__(self, result, *, subscription=None, channel=None): super(PNMessageActionResult, self).__init__(result) + self.subscription = subscription + self.channel = channel class PNPublishResult(object): diff --git a/pubnub/models/subscription.py b/pubnub/models/subscription.py new file mode 100644 index 00000000..fe2e47b0 --- /dev/null +++ b/pubnub/models/subscription.py @@ -0,0 +1,332 @@ +from enum import Enum +from typing import List, Optional, Union + +from pubnub.callbacks import SubscribeCallback +from pubnub.dtos import SubscribeOperation, UnsubscribeOperation + + +class PNSubscriptionType(Enum): + CHANNEL: str = "channel" + CHANNEL_GROUP: str = "channel_group" + + +class PNSubscribable: + pubnub = None + name: str + _type: PNSubscriptionType = None + + def __init__(self, pubnub_instance, name) -> None: + self.pubnub = pubnub_instance + self.name = name + + def subscription(self, with_presence: bool = None): + return PubNubSubscription(self.pubnub, self.name, self._type, with_presence=with_presence) + + +class PNEventEmitter: + on_message: callable + on_signal: callable + on_presence: callable + on_channel_metadata: callable + on_user_metadata: callable + on_message_action: callable + on_membership: callable + on_file: callable + + def is_matching_listener(self, message): + def wildcard_match(name, subscription): + return subscription.endswith('.*') and name.startswith(subscription.strip('*')) + if isinstance(self, PubNubSubscriptionSet): + return any([subscription_item.is_matching_listener(message) + for subscription_item in self.get_subscription_items()]) + else: + if self._type == PNSubscriptionType.CHANNEL: + return message.channel == self.name or wildcard_match(message.channel, self.name) + else: + return message.subscription == self.name + + def presence(self, presence): + if not hasattr(self, 'on_presence') or not (hasattr(self, 'with_presence') and self.with_presence): + return + + if self.is_matching_listener(presence) and hasattr(self, 'on_presence'): + self.on_presence(presence) + + def message(self, message): + if self.is_matching_listener(message) and hasattr(self, 'on_message'): + self.on_message(message) + + def message_action(self, message_action): + if self.is_matching_listener(message_action) and hasattr(self, 'on_message_action'): + self.on_message_action(message_action) + + def signal(self, signal): + if self.is_matching_listener(signal) and hasattr(self, 'on_signal'): + self.on_signal(signal) + + +class PNSubscribeCapable: + def subscribe(self, timetoken: Optional[int] = None, region: Optional[str] = None): + self.timetoken = timetoken + self.region = region + self.subscription_registry.add(self) + + def unsubscribe(self): + self.subscription_registry.remove(self) + + +class PubNubSubscription(PNEventEmitter, PNSubscribeCapable): + def __init__(self, pubnub_instance, name: str, type: PNSubscriptionType, with_presence: bool = False) -> None: + self.subscription_registry = pubnub_instance._subscription_registry + self.subscription_manager = pubnub_instance._subscription_manager + self.name = name + self._type = type + self.with_presence = with_presence + + def add_listener(self, listener): + self.subscription_registry.add_listener(listener) + + def get_names_with_presence(self): + return [self.name, f'{self.name}-pnpres'] if self.with_presence else [self.name] + + +class PubNubSubscriptionSet(PNEventEmitter, PNSubscribeCapable): + def __init__(self, pubnub_instance, subscriptions: List[PubNubSubscription]) -> None: + self.subscription_registry = pubnub_instance._subscription_registry + self.subscription_manager = pubnub_instance._subscription_manager + self.subscriptions = subscriptions + + def get_subscription_items(self): + return [item for item in self.subscriptions] + + +class PubNubChannel(PNSubscribable): + _type = PNSubscriptionType.CHANNEL + + def __init__(self, pubnub_instance, channel: str) -> None: + super().__init__(pubnub_instance, channel) + + +class PubNubChannelGroup(PNSubscribable): + _type = PNSubscriptionType.CHANNEL_GROUP + + def __init__(self, pubnub_instance, channel_group: str) -> None: + super().__init__(pubnub_instance, channel_group) + + +class PubNubChannelMetadata(PNSubscribable): + _type = PNSubscriptionType.CHANNEL + + def __init__(self, pubnub_instance, channel: str) -> None: + super().__init__(pubnub_instance, channel) + + +class PubNubUserMetadata(PNSubscribable): + _types = PNSubscriptionType.CHANNEL + + def __init__(self, pubnub_instance, user_id: str) -> None: + super().__init__(pubnub_instance, user_id) + + +class PNSubscriptionRegistry: + def __init__(self, pubnub_instance): + self.pubnub = pubnub_instance + self.global_listeners = [] + self.channels = {} + self.channel_groups = {} + self.subscription_registry_callback = None + self.with_presence = None + self.subscriptions = [] + + def __add_subscription(self, subscription: PubNubSubscription, subscription_set: PubNubSubscriptionSet = None): + names_added = [] + self.subscriptions.append(subscription) + + subscriptions = [subscription] + if subscription_set: + subscriptions.append(subscription_set) + + if subscription._type == PNSubscriptionType.CHANNEL: + subscription_list = self.channels + else: + subscription_list = self.channel_groups + + for name in subscription.get_names_with_presence(): + if name not in subscription_list: + subscription_list[name] = subscriptions + names_added.append(name) + else: + subscription_list[name].extend(subscriptions) + return names_added + + def __remove_subscription(self, subscription: PubNubSubscription): + names_removed = {'channels': [], + 'groups': []} + + self.subscriptions.remove(subscription) + + if subscription._type == PNSubscriptionType.CHANNEL: + subscription_list = self.channels + removed = names_removed['channels'] + else: + subscription_list = self.channel_groups + removed = names_removed['groups'] + + for name in subscription.get_names_with_presence(): + if name in subscription_list and subscription in subscription_list[name]: + subscription_list[name].remove(subscription) + if len(subscription_list[name]) == 0: + removed.append(name) + + return names_removed + + def add(self, subscription: Union[PubNubSubscription, PubNubSubscriptionSet]) -> list: + if not self.subscription_registry_callback: + self.subscription_registry_callback = PNSubscriptionRegistryCallback(self) + self.pubnub.add_listener(self.subscription_registry_callback) + + self.with_presence = any(sub.with_presence for sub in self.subscriptions) + + names_changed = [] + if isinstance(subscription, PubNubSubscriptionSet): + for subscription_part in subscription.subscriptions: + names_changed.append(self.__add_subscription(subscription_part, subscription)) + else: + names_changed.append(self.__add_subscription(subscription)) + + tt = self.pubnub._subscription_manager._timetoken + if subscription.timetoken: + tt = max(subscription.timetoken, self.pubnub._subscription_manager._timetoken) + + if names_changed: + subscribe_operation = SubscribeOperation( + channels=self.get_subscribed_channels(), + channel_groups=self.get_subscribed_channel_groups(), + timetoken=tt, + presence_enabled=self.with_presence, + ) + self.pubnub._subscription_manager.adapt_subscribe_builder(subscribe_operation) + return names_changed + + def remove(self, subscription: Union[PubNubSubscription, PubNubSubscriptionSet]) -> list: + channels_changed = [] + groups_changed = [] + + if isinstance(subscription, PubNubSubscriptionSet): + for subscription_part in subscription.subscriptions: + names_changed = self.__remove_subscription(subscription_part) + channels_changed += names_changed['channels'] + groups_changed += names_changed['groups'] + else: + names_changed = self.__remove_subscription(subscription) + channels_changed += names_changed['channels'] + groups_changed += names_changed['groups'] + + self.with_presence = any(sub.with_presence for sub in self.subscriptions) + + if names_changed: + unsubscribe_operation = UnsubscribeOperation(channels=channels_changed, channel_groups=groups_changed) + self.pubnub._subscription_manager.adapt_unsubscribe_builder(unsubscribe_operation) + return names_changed + + def get_subscribed_channels(self): + return list(self.channels.keys()) + + def get_subscribed_channel_groups(self): + return list(self.channel_groups.keys()) + + def get_subscriptions_for(self, _type: PNSubscriptionType, name: str): + if _type == PNSubscriptionType.CHANNEL: + return [channel for channel in self.get_subscribed_channels() if channel == name] + else: + return [group for group in self.get_subscribed_channel_groups() if group == name] + + def get_all_listeners(self): + listeners = [] + + for channel in self.channels: + listeners += self.channels[channel] + for channel_group in self.channel_groups: + listeners += self.channel_groups[channel_group] + if self.global_listeners: + listeners += self.global_listeners + return set(listeners) + + def add_listener(self, listener): + assert isinstance(listener, SubscribeCallback) + self.global_listeners.append(listener) + + def remove_listener(self, listener): + assert isinstance(listener, SubscribeCallback) + self.global_listeners.remove(listener) + + def unsubscribe_all(self): + unsubscribe_operation = UnsubscribeOperation( + channels=list(self.channels.keys()), + channel_groups=list(self.channel_groups.keys()) + ) + self.pubnub._subscription_manager.adapt_unsubscribe_builder(unsubscribe_operation) + self.channels = [] + self.channel_groups = [] + + def unsubscribe(self, channels=None, groups=None): + presence_channels = [] + for channel in channels: + del self.channels[channel] + if f'{channel}-pnpres' in self.channels: + del self.channels[f'{channel}-pnpres'] + presence_channels.append(f'{channel}-pnpres') + + presence_groups = [] + for group in groups: + del self.channel_groups[group] + if f'{group}-pnpres' in self.channel_groups: + del self.channel_groups[f'{group}-pnpres'] + presence_groups.append(f'{group}-pnpres') + + unsubscribe_operation = UnsubscribeOperation( + channels=channels + presence_channels, + channel_groups=groups + presence_groups + ) + self.pubnub._subscription_manager.adapt_unsubscribe_builder(unsubscribe_operation) + + +class PNSubscriptionRegistryCallback(SubscribeCallback): + def __init__(self, subscription_registry: PNSubscriptionRegistry) -> None: + self.subscription_registry = subscription_registry + super().__init__() + + def status(self, _, status): + pass + + def presence(self, _, presence): + for listener in self.subscription_registry.get_all_listeners(): + listener.presence(presence) + + def message(self, _, message): + for listener in self.subscription_registry.get_all_listeners(): + listener.message(message) + + def signal(self, _, signal): + for listener in self.subscription_registry.get_all_listeners(): + listener.signal(signal) + + def channel(self, _, channel): + for listener in self.subscription_registry.get_all_listeners(): + listener.channel(channel) + + def uuid(self, pubnub, uuid): + for listener in self.subscription_registry.get_all_listeners(): + listener.uuid(uuid) + + def membership(self, _, membership): + for listener in self.subscription_registry.get_all_listeners(): + listener.membership(membership) + + def message_action(self, _, message_action): + for listener in self.subscription_registry.get_all_listeners(): + listener.message_action(message_action) + + def file(self, _, file_message): + for listener in self.subscription_registry.get_all_listeners(): + listener.file_message(file_message) diff --git a/pubnub/pubnub.py b/pubnub/pubnub.py index 235e6b33..94bc201e 100644 --- a/pubnub/pubnub.py +++ b/pubnub/pubnub.py @@ -356,7 +356,7 @@ def _run(self): def _schedule_next(self): self._timeout = threading.Timer(self._callback_time, self._run) - self._timer.daemon = True + self._timeout.daemon = True self._timeout.start() diff --git a/pubnub/pubnub_asyncio.py b/pubnub/pubnub_asyncio.py index 2822023e..e19d6ca0 100644 --- a/pubnub/pubnub_asyncio.py +++ b/pubnub/pubnub_asyncio.py @@ -17,7 +17,6 @@ from pubnub.endpoints.presence.heartbeat import Heartbeat from pubnub.endpoints.presence.leave import Leave from pubnub.endpoints.pubsub.subscribe import Subscribe -from pubnub.features import feature_enabled from pubnub.pubnub_core import PubNubCore from pubnub.workers import SubscribeMessageWorker from pubnub.managers import SubscriptionManager, PublishSequenceManager, ReconnectionManager, TelemetryManager @@ -49,9 +48,7 @@ def __init__(self, config, custom_event_loop=None, subscription_manager=None): self._connector = aiohttp.TCPConnector(verify_ssl=True, loop=self.event_loop) if not subscription_manager: - subscription_manager = ( - EventEngineSubscriptionManager if feature_enabled('PN_ENABLE_EVENT_ENGINE') - else AsyncioSubscriptionManager) + subscription_manager = EventEngineSubscriptionManager if self.config.enable_subscribe: self._subscription_manager = subscription_manager(self) @@ -179,7 +176,6 @@ async def _request_helper(self, options_func, cancellation_event): url = utils.build_url(scheme="", origin="", path=options.path, params=options.query_string) url = URL(url, encoded=True) - logger.debug("%s %s %s" % (options.method_string, url, options.data)) if options.request_headers: @@ -566,6 +562,7 @@ def __init__(self, pubnub_instance): self.event_engine.get_dispatcher().set_pn(pubnub_instance) self.presence_engine.get_dispatcher().set_pn(pubnub_instance) self.loop = asyncio.new_event_loop() + self._heartbeat_periodic_callback = None pubnub_instance.state_container = self.state_container super().__init__(pubnub_instance) @@ -593,21 +590,17 @@ def adapt_subscribe_builder(self, subscribe_operation: SubscribeOperation): self.event_engine.trigger(subscription_event) if self._pubnub.config.enable_presence_heartbeat and self._pubnub.config._heartbeat_interval > 0: self.presence_engine.trigger(events.HeartbeatJoinedEvent( - channels=subscribe_operation.channels, - groups=subscribe_operation.channel_groups + channels=subscribe_operation.channels_without_presence, + groups=subscribe_operation.channel_groups_without_presence )) def adapt_unsubscribe_builder(self, unsubscribe_operation): if not isinstance(unsubscribe_operation, UnsubscribeOperation): raise PubNubException('Invalid Unsubscribe Operation') - channels = unsubscribe_operation.get_subscribed_channels( - self.event_engine.get_context().channels, - self.event_engine.get_context().with_presence) + channels = unsubscribe_operation.get_subscribed_channels(self.event_engine.get_context().channels) - groups = unsubscribe_operation.get_subscribed_channel_groups( - self.event_engine.get_context().groups, - self.event_engine.get_context().with_presence) + groups = unsubscribe_operation.get_subscribed_channel_groups(self.event_engine.get_context().groups) if channels or groups: self.event_engine.trigger(events.SubscriptionChangedEvent(channels=channels, groups=groups)) @@ -616,8 +609,8 @@ def adapt_unsubscribe_builder(self, unsubscribe_operation): if self._pubnub.config.enable_presence_heartbeat and self._pubnub.config._heartbeat_interval > 0: self.presence_engine.trigger(event=events.HeartbeatLeftEvent( - channels=unsubscribe_operation.channels, - groups=unsubscribe_operation.channel_groups, + channels=unsubscribe_operation.channels_without_presence, + groups=unsubscribe_operation.channel_groups_without_presence, suppress_leave=self._pubnub.config.suppress_leave_events )) diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index 29e60fdd..e5c615e8 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -20,6 +20,8 @@ from pubnub.exceptions import PubNubException from pubnub.features import feature_flag from pubnub.crypto import PubNubCryptoModule +from pubnub.models.subscription import PubNubChannel, PubNubChannelGroup, PubNubChannelMetadata, PubNubUserMetadata, \ + PNSubscriptionRegistry, PubNubSubscriptionSet from abc import ABCMeta, abstractmethod @@ -85,7 +87,7 @@ class PubNubCore: """A base class for PubNub Python API implementations""" - SDK_VERSION = "7.4.4" + SDK_VERSION = "8.0.0" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 @@ -94,6 +96,8 @@ class PubNubCore: __metaclass__ = ABCMeta __crypto = None + _subscription_registry: PNSubscriptionRegistry + def __init__(self, config): self.config = config self.config.validate() @@ -106,6 +110,7 @@ def __init__(self, config): self._telemetry_manager = TelemetryManager() self._base_path_manager = BasePathManager(config) self._token_manager = TokenManager() + self._subscription_registry = PNSubscriptionRegistry(self) @property def base_origin(self): @@ -164,16 +169,16 @@ def remove_channel_group(self): return RemoveChannelGroup(self) def subscribe(self): - return SubscribeBuilder(self._subscription_manager) + return SubscribeBuilder(self) def unsubscribe(self): - return UnsubscribeBuilder(self._subscription_manager) + return UnsubscribeBuilder(self) def unsubscribe_all(self): - return self._subscription_manager.unsubscribe_all() + return self._subscription_registry.unsubscribe_all() def reconnect(self): - return self._subscription_manager.reconnect() + return self._subscription_registry.reconnect() def heartbeat(self): return Heartbeat(self) @@ -640,3 +645,18 @@ def fetch_memberships(self, user_id: str = None, space_id: str = None, limit=Non if sync: return memberships.sync() return memberships + + def channel(self, channel) -> PubNubChannel: + return PubNubChannel(self, channel) + + def channel_group(self, channel_group) -> PubNubChannelGroup: + return PubNubChannelGroup(self, channel_group) + + def channel_metadata(self, channel) -> PubNubChannelMetadata: + return PubNubChannelMetadata(self, channel) + + def user_metadata(self, user_id) -> PubNubUserMetadata: + return PubNubUserMetadata(self, user_id) + + def subscription_set(self, subscriptions: list) -> PubNubSubscriptionSet: + return PubNubSubscriptionSet(self, subscriptions) diff --git a/pubnub/request_handlers/requests_handler.py b/pubnub/request_handlers/requests_handler.py index 1667ef78..1b29068d 100644 --- a/pubnub/request_handlers/requests_handler.py +++ b/pubnub/request_handlers/requests_handler.py @@ -55,7 +55,6 @@ def callback_to_invoke_in_separate_thread(): # Since there are no way to affect on ongoing request it's response will # be just ignored on cancel call return - callback(envelope) except PubNubException as e: logger.error("Async request PubNubException. %s" % str(e)) @@ -68,6 +67,7 @@ def callback_to_invoke_in_separate_thread(): exception=e))) except Exception as e: logger.error("Async request Exception. %s" % str(e)) + callback(Envelope( result=None, status=endpoint_call_options.create_status( diff --git a/pubnub/workers.py b/pubnub/workers.py index 70a18d30..5024771e 100644 --- a/pubnub/workers.py +++ b/pubnub/workers.py @@ -191,8 +191,8 @@ def _process_incoming_payload(self, message: SubscribeMessage): message_action = extracted_message['data'] if 'uuid' not in message_action: message_action['uuid'] = publisher - - message_action_result = PNMessageActionResult(message_action) + message_action_result = PNMessageActionResult(message_action, subscription=subscription_match, + channel=channel) self._listener_manager.announce_message_action(message_action_result) else: diff --git a/setup.py b/setup.py index d131655d..3d9105ba 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='pubnub', - version='7.4.4', + version='8.0.0', description='PubNub Real-time push service in the cloud', author='PubNub', author_email='support@pubnub.com', diff --git a/tests/helper.py b/tests/helper.py index 0bbbf42d..2a55782b 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -33,7 +33,7 @@ crypto_configuration = PNConfiguration() crypto = PubNubCryptodome(crypto_configuration) -crypto.subscribe_request_timeout = 10 +crypto.subscribe_request_timeout = 20 DEFAULT_TEST_CIPHER_KEY = "testKey" @@ -60,6 +60,8 @@ pnconf_sub.subscribe_request_timeout = 10 pnconf_sub.subscribe_key = sub_key pnconf_sub.uuid = uuid_mock +pnconf_sub.enable_presence_heartbeat = True +pnconf_sub.set_presence_timeout_with_custom_interval(30, 10) pnconf_enc = PNConfiguration() pnconf_enc.publish_key = pub_key diff --git a/tests/integrational/asyncio/test_heartbeat.py b/tests/integrational/asyncio/test_heartbeat.py index 6e720d29..b80351e5 100644 --- a/tests/integrational/asyncio/test_heartbeat.py +++ b/tests/integrational/asyncio/test_heartbeat.py @@ -5,28 +5,21 @@ import pubnub as pn from pubnub.pubnub_asyncio import AsyncioSubscriptionManager, PubNubAsyncio, SubscribeListener from tests import helper -from tests.helper import pnconf_sub_copy +from tests.helper import pnconf_env_copy pn.set_stream_logger('pubnub', logging.DEBUG) -messenger_config = pnconf_sub_copy() -messenger_config.set_presence_timeout(8) -messenger_config.uuid = helper.gen_channel("messenger") - -listener_config = pnconf_sub_copy() -listener_config.uuid = helper.gen_channel("listener") - - @pytest.mark.asyncio -async def test_timeout_event_on_broken_heartbeat(event_loop): +async def test_timeout_event_on_broken_heartbeat(): ch = helper.gen_channel("heartbeat-test") - pubnub = PubNubAsyncio(messenger_config, custom_event_loop=event_loop) - pubnub_listener = PubNubAsyncio(listener_config, custom_event_loop=event_loop) + messenger_config = pnconf_env_copy(uuid=helper.gen_channel("messenger"), enable_subscribe=True) + messenger_config.set_presence_timeout(8) + pubnub = PubNubAsyncio(messenger_config) - pubnub.config.uuid = helper.gen_channel("messenger") - pubnub_listener.config.uuid = helper.gen_channel("listener") + listener_config = pnconf_env_copy(uuid=helper.gen_channel("listener"), enable_subscribe=True) + pubnub_listener = PubNubAsyncio(listener_config) # - connect to :ch-pnpres callback_presence = SubscribeListener() @@ -39,7 +32,7 @@ async def test_timeout_event_on_broken_heartbeat(event_loop): assert 'join' == envelope.event assert pubnub_listener.uuid == envelope.uuid - # - connect to :ch + # # - connect to :ch callback_messages = SubscribeListener() pubnub.add_listener(callback_messages) pubnub.subscribe().channels(ch).execute() @@ -55,13 +48,12 @@ async def test_timeout_event_on_broken_heartbeat(event_loop): assert ch == prs_envelope.channel assert 'join' == prs_envelope.event assert pubnub.uuid == prs_envelope.uuid + # - break messenger heartbeat loop + pubnub._subscription_manager._stop_heartbeat_timer() # wait for one heartbeat call await asyncio.sleep(8) - # - break messenger heartbeat loop - pubnub._subscription_manager._stop_heartbeat_timer() - # - assert for timeout envelope = await callback_presence.wait_for_presence_on(ch) assert ch == envelope.channel diff --git a/tests/integrational/asyncio/test_subscribe.py b/tests/integrational/asyncio/test_subscribe.py index c103bbbf..5760d0ae 100644 --- a/tests/integrational/asyncio/test_subscribe.py +++ b/tests/integrational/asyncio/test_subscribe.py @@ -6,7 +6,7 @@ from unittest.mock import patch from pubnub.models.consumer.pubsub import PNMessageResult from pubnub.pubnub_asyncio import AsyncioSubscriptionManager, PubNubAsyncio, AsyncioEnvelope, SubscribeListener -from tests.helper import pnconf_enc_env_copy, pnconf_env_copy +from tests.helper import gen_channel, pnconf_enc_env_copy, pnconf_env_copy, pnconf_sub_copy from tests.integrational.vcr_asyncio_sleeper import VCR599Listener, VCR599ReconnectionManager # from tests.integrational.vcr_helper import pn_vcr @@ -152,19 +152,23 @@ async def test_encrypted_subscribe_publish_unsubscribe(): # @pn_vcr.use_cassette('tests/integrational/fixtures/asyncio/subscription/join_leave.yaml', -# filter_query_parameters=['pnsdk', 'l_cg']) +# filter_query_parameters=['pnsdk', 'l_cg', 'ee']) @pytest.mark.asyncio async def test_join_leave(): - channel = "test-subscribe-asyncio-join-leave-ch" + channel = gen_channel("test-subscribe-asyncio-join-leave-ch") + pubnub_config = pnconf_sub_copy() + pubnub_config.uuid = "test-subscribe-asyncio-messenger" + pubnub = PubNubAsyncio(pubnub_config) - pubnub = PubNubAsyncio(pnconf_env_copy(enable_subscribe=True, uuid="test-subscribe-asyncio-messenger")) - pubnub_listener = PubNubAsyncio(pnconf_env_copy(enable_subscribe=True, uuid="test-subscribe-asyncio-listener")) + listener_config = pnconf_sub_copy() + listener_config.uuid = "test-subscribe-asyncio-listener" + pubnub_listener = PubNubAsyncio(listener_config) await patch_pubnub(pubnub) await patch_pubnub(pubnub_listener) - callback_presence = VCR599Listener(1) - callback_messages = VCR599Listener(1) + callback_presence = SubscribeListener() + callback_messages = SubscribeListener() pubnub_listener.add_listener(callback_presence) pubnub_listener.subscribe().channels(channel).with_presence().execute() @@ -282,23 +286,27 @@ async def test_cg_subscribe_publish_unsubscribe(): await pubnub.stop() -@pytest.mark.skip # @pn_vcr.use_cassette('tests/integrational/fixtures/asyncio/subscription/cg_join_leave.json', serializer='pn_json', # filter_query_parameters=['pnsdk', 'l_cg', 'l_pres', 'ee', 'tr']) @pytest.mark.asyncio async def test_cg_join_leave(): - pubnub = PubNubAsyncio(pnconf_env_copy(enable_subscribe=True, uuid="test-subscribe-asyncio-messenger")) - pubnub_listener = PubNubAsyncio(pnconf_env_copy(enable_subscribe=True, uuid="test-subscribe-asyncio-listener")) + config = pnconf_sub_copy() + config.uuid = "test-subscribe-asyncio-messenger" + pubnub = PubNubAsyncio(config) - ch = "test-subscribe-asyncio-join-leave-cg-channel" - gr = "test-subscribe-asyncio-join-leave-cg-group" + config_listener = pnconf_sub_copy() + config_listener.uuid = "test-subscribe-asyncio-listener" + pubnub_listener = PubNubAsyncio(config_listener) + + ch = gen_channel("test-subscribe-asyncio-join-leave-cg-channel") + gr = gen_channel("test-subscribe-asyncio-join-leave-cg-group") envelope = await pubnub.add_channel_to_channel_group().channel_group(gr).channels(ch).future() assert envelope.status.original_response['status'] == 200 await asyncio.sleep(1) - callback_messages = VCR599Listener(1) - callback_presence = VCR599Listener(1) + callback_messages = SubscribeListener() + callback_presence = SubscribeListener() pubnub_listener.add_listener(callback_presence) pubnub_listener.subscribe().channel_groups(gr).with_presence().execute() diff --git a/tests/integrational/fixtures/asyncio/file_upload/send_and_download_encrypted_file_cipher_key.json b/tests/integrational/fixtures/asyncio/file_upload/send_and_download_encrypted_file_cipher_key.json index c17f4169..0d103f6a 100644 --- a/tests/integrational/fixtures/asyncio/file_upload/send_and_download_encrypted_file_cipher_key.json +++ b/tests/integrational/fixtures/asyncio/file_upload/send_and_download_encrypted_file_cipher_key.json @@ -8,7 +8,7 @@ "body": "{\"name\": \"king_arthur.txt\"}", "headers": { "User-Agent": [ - "PubNub-Python-Asyncio/7.2.0" + "PubNub-Python-Asyncio/7.4.2" ], "Content-type": [ "application/json" @@ -22,7 +22,7 @@ }, "headers": { "Date": [ - "Wed, 04 Oct 2023 21:18:28 GMT" + "Wed, 27 Mar 2024 14:15:16 GMT" ], "Content-Type": [ "application/json" @@ -38,7 +38,7 @@ ] }, "body": { - "string": "{\"status\":200,\"data\":{\"id\":\"f132fed8-04a4-4365-837b-7fd65cebea1d\",\"name\":\"king_arthur.txt\"},\"file_upload_request\":{\"url\":\"https://pubnub-mnemosyne-files-eu-central-1-prd.s3.eu-central-1.amazonaws.com/\",\"method\":\"POST\",\"expiration_date\":\"2023-10-04T21:19:28Z\",\"form_fields\":[{\"key\":\"tagging\",\"value\":\"\\u003cTagging\\u003e\\u003cTagSet\\u003e\\u003cTag\\u003e\\u003cKey\\u003eObjectTTLInDays\\u003c/Key\\u003e\\u003cValue\\u003e1\\u003c/Value\\u003e\\u003c/Tag\\u003e\\u003c/TagSet\\u003e\\u003c/Tagging\\u003e\"},{\"key\":\"key\",\"value\":\"{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/f132fed8-04a4-4365-837b-7fd65cebea1d/king_arthur.txt\"},{\"key\":\"Content-Type\",\"value\":\"text/plain; charset=utf-8\"},{\"key\":\"X-Amz-Credential\",\"value\":\"AKIAY7AU6GQDV5LCPVEX/20231004/eu-central-1/s3/aws4_request\"},{\"key\":\"X-Amz-Security-Token\",\"value\":\"\"},{\"key\":\"X-Amz-Algorithm\",\"value\":\"AWS4-HMAC-SHA256\"},{\"key\":\"X-Amz-Date\",\"value\":\"20231004T211928Z\"},{\"key\":\"Policy\",\"value\":\"CnsKCSJleHBpcmF0aW9uIjogIjIwMjMtMTAtMDRUMjE6MTk6MjhaIiwKCSJjb25kaXRpb25zIjogWwoJCXsiYnVja2V0IjogInB1Ym51Yi1tbmVtb3N5bmUtZmlsZXMtZXUtY2VudHJhbC0xLXByZCJ9LAoJCVsiZXEiLCAiJHRhZ2dpbmciLCAiPFRhZ2dpbmc+PFRhZ1NldD48VGFnPjxLZXk+T2JqZWN0VFRMSW5EYXlzPC9LZXk+PFZhbHVlPjE8L1ZhbHVlPjwvVGFnPjwvVGFnU2V0PjwvVGFnZ2luZz4iXSwKCQlbImVxIiwgIiRrZXkiLCAic3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvZjEzMmZlZDgtMDRhNC00MzY1LTgzN2ItN2ZkNjVjZWJlYTFkL2tpbmdfYXJ0aHVyLnR4dCJdLAoJCVsiY29udGVudC1sZW5ndGgtcmFuZ2UiLCAwLCA1MjQyODgwXSwKCQlbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiIl0sCgkJeyJ4LWFtei1jcmVkZW50aWFsIjogIkFLSUFZN0FVNkdRRFY1TENQVkVYLzIwMjMxMDA0L2V1LWNlbnRyYWwtMS9zMy9hd3M0X3JlcXVlc3QifSwKCQl7IngtYW16LXNlY3VyaXR5LXRva2VuIjogIiJ9LAoJCXsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwKCQl7IngtYW16LWRhdGUiOiAiMjAyMzEwMDRUMjExOTI4WiIgfQoJXQp9Cg==\"},{\"key\":\"X-Amz-Signature\",\"value\":\"e075eaec32901853278dbcaf2ce2b5644334eabe3e759f983f0fa5c300eac4d5\"}]}}" + "string": "{\"status\":200,\"data\":{\"id\":\"4cee979e-98a6-4019-83f9-a8506e7333e9\",\"name\":\"king_arthur.txt\"},\"file_upload_request\":{\"url\":\"https://pubnub-mnemosyne-files-eu-central-1-prd.s3.eu-central-1.amazonaws.com/\",\"method\":\"POST\",\"expiration_date\":\"2024-03-27T14:16:16Z\",\"form_fields\":[{\"key\":\"tagging\",\"value\":\"\\u003cTagging\\u003e\\u003cTagSet\\u003e\\u003cTag\\u003e\\u003cKey\\u003eObjectTTLInDays\\u003c/Key\\u003e\\u003cValue\\u003e1\\u003c/Value\\u003e\\u003c/Tag\\u003e\\u003c/TagSet\\u003e\\u003c/Tagging\\u003e\"},{\"key\":\"key\",\"value\":\"{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/4cee979e-98a6-4019-83f9-a8506e7333e9/king_arthur.txt\"},{\"key\":\"Content-Type\",\"value\":\"text/plain; charset=utf-8\"},{\"key\":\"X-Amz-Credential\",\"value\":\"AKIAY7AU6GQDV5LCPVEX/20240327/eu-central-1/s3/aws4_request\"},{\"key\":\"X-Amz-Security-Token\",\"value\":\"\"},{\"key\":\"X-Amz-Algorithm\",\"value\":\"AWS4-HMAC-SHA256\"},{\"key\":\"X-Amz-Date\",\"value\":\"20240327T141616Z\"},{\"key\":\"Policy\",\"value\":\"CnsKCSJleHBpcmF0aW9uIjogIjIwMjQtMDMtMjdUMTQ6MTY6MTZaIiwKCSJjb25kaXRpb25zIjogWwoJCXsiYnVja2V0IjogInB1Ym51Yi1tbmVtb3N5bmUtZmlsZXMtZXUtY2VudHJhbC0xLXByZCJ9LAoJCVsiZXEiLCAiJHRhZ2dpbmciLCAiPFRhZ2dpbmc+PFRhZ1NldD48VGFnPjxLZXk+T2JqZWN0VFRMSW5EYXlzPC9LZXk+PFZhbHVlPjE8L1ZhbHVlPjwvVGFnPjwvVGFnU2V0PjwvVGFnZ2luZz4iXSwKCQlbImVxIiwgIiRrZXkiLCAic3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvNGNlZTk3OWUtOThhNi00MDE5LTgzZjktYTg1MDZlNzMzM2U5L2tpbmdfYXJ0aHVyLnR4dCJdLAoJCVsiY29udGVudC1sZW5ndGgtcmFuZ2UiLCAwLCA1MjQyODgwXSwKCQlbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiIl0sCgkJeyJ4LWFtei1jcmVkZW50aWFsIjogIkFLSUFZN0FVNkdRRFY1TENQVkVYLzIwMjQwMzI3L2V1LWNlbnRyYWwtMS9zMy9hd3M0X3JlcXVlc3QifSwKCQl7IngtYW16LXNlY3VyaXR5LXRva2VuIjogIiJ9LAoJCXsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwKCQl7IngtYW16LWRhdGUiOiAiMjAyNDAzMjdUMTQxNjE2WiIgfQoJXQp9Cg==\"},{\"key\":\"X-Amz-Signature\",\"value\":\"2b4c77b2bfdd08bf83b5bb642d4b0062da19f04e09fb7b5c1b856c2d8d16d956\"}]}}" } } }, @@ -47,11 +47,11 @@ "method": "POST", "uri": "https://pubnub-mnemosyne-files-eu-central-1-prd.s3.eu-central-1.amazonaws.com/", "body": { - "pickle": "gASVQBIAAAAAAACMEGFpb2h0dHAuZm9ybWRhdGGUjAhGb3JtRGF0YZSTlCmBlH2UKIwHX3dyaXRlcpSMEWFpb2h0dHAubXVsdGlwYXJ0lIwPTXVsdGlwYXJ0V3JpdGVylJOUKYGUfZQojAlfYm91bmRhcnmUQyAwOWQxOWYyYTM0ZGY0ZDM2OWVhMmY2YWExMzk3YjVhMZSMCV9lbmNvZGluZ5ROjAlfZmlsZW5hbWWUTowIX2hlYWRlcnOUjBRtdWx0aWRpY3QuX211bHRpZGljdJSMC0NJTXVsdGlEaWN0lJOUXZRoEIwEaXN0cpSTlIwMQ29udGVudC1UeXBllIWUgZSMPm11bHRpcGFydC9mb3JtLWRhdGE7IGJvdW5kYXJ5PTA5ZDE5ZjJhMzRkZjRkMzY5ZWEyZjZhYTEzOTdiNWExlIaUYYWUUpSMBl92YWx1ZZROjAZfcGFydHOUXZQojA9haW9odHRwLnBheWxvYWSUjA1TdHJpbmdQYXlsb2FklJOUKYGUfZQoaA2MBXV0Zi04lGgOTmgPaBJdlChoGIwTbXVsdGlwYXJ0L2Zvcm0tZGF0YZSGlGgVjBNDb250ZW50LURpc3Bvc2l0aW9ulIWUgZSMGWZvcm0tZGF0YTsgbmFtZT0idGFnZ2luZyKUhpRoFYwOQ29udGVudC1MZW5ndGiUhZSBlIwCODmUhpRlhZRSlGgdQ1k8VGFnZ2luZz48VGFnU2V0PjxUYWc+PEtleT5PYmplY3RUVExJbkRheXM8L0tleT48VmFsdWU+MTwvVmFsdWU+PC9UYWc+PC9UYWdTZXQ+PC9UYWdnaW5nPpSMBV9zaXpllEtZdWKMAJRoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjBVmb3JtLWRhdGE7IG5hbWU9ImtleSKUhpRoMIwDMTM5lIaUZYWUUpRoHUOLc3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvZjEzMmZlZDgtMDRhNC00MzY1LTgzN2ItN2ZkNjVjZWJlYTFkL2tpbmdfYXJ0aHVyLnR4dJRoNkuLdWJoN2g3h5RoIimBlH2UKGgNaCVoDk5oD2gSXZQoaBhoJ4aUaCuMHmZvcm0tZGF0YTsgbmFtZT0iQ29udGVudC1UeXBlIpSGlGgwjAIyNZSGlGWFlFKUaB1DGXRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTiUaDZLGXViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCJmb3JtLWRhdGE7IG5hbWU9IlgtQW16LUNyZWRlbnRpYWwilIaUaDCMAjU4lIaUZYWUUpRoHUM6QUtJQVk3QVU2R1FEVjVMQ1BWRVgvMjAyMzEwMDQvZXUtY2VudHJhbC0xL3MzL2F3czRfcmVxdWVzdJRoNks6dWJoN2g3h5RoIimBlH2UKGgNaCVoDk5oD2gSXZQoaBhoJ4aUaCuMJmZvcm0tZGF0YTsgbmFtZT0iWC1BbXotU2VjdXJpdHktVG9rZW4ilIaUaDCMATCUhpRlhZRSlGgdQwCUaDZLAHViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCFmb3JtLWRhdGE7IG5hbWU9IlgtQW16LUFsZ29yaXRobSKUhpRoMIwCMTaUhpRlhZRSlGgdQxBBV1M0LUhNQUMtU0hBMjU2lGg2SxB1Ymg3aDeHlGgiKYGUfZQoaA1oJWgOTmgPaBJdlChoGGgnhpRoK4wcZm9ybS1kYXRhOyBuYW1lPSJYLUFtei1EYXRlIpSGlGgwjAIxNpSGlGWFlFKUaB1DEDIwMjMxMDA0VDIxMTkyOFqUaDZLEHViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjBhmb3JtLWRhdGE7IG5hbWU9IlBvbGljeSKUhpRoMIwDOTA0lIaUZYWUUpRoHUKIAwAAQ25zS0NTSmxlSEJwY21GMGFXOXVJam9nSWpJd01qTXRNVEF0TURSVU1qRTZNVGs2TWpoYUlpd0tDU0pqYjI1a2FYUnBiMjV6SWpvZ1d3b0pDWHNpWW5WamEyVjBJam9nSW5CMVltNTFZaTF0Ym1WdGIzTjVibVV0Wm1sc1pYTXRaWFV0WTJWdWRISmhiQzB4TFhCeVpDSjlMQW9KQ1ZzaVpYRWlMQ0FpSkhSaFoyZHBibWNpTENBaVBGUmhaMmRwYm1jK1BGUmhaMU5sZEQ0OFZHRm5QanhMWlhrK1QySnFaV04wVkZSTVNXNUVZWGx6UEM5TFpYaytQRlpoYkhWbFBqRThMMVpoYkhWbFBqd3ZWR0ZuUGp3dlZHRm5VMlYwUGp3dlZHRm5aMmx1Wno0aVhTd0tDUWxiSW1WeElpd2dJaVJyWlhraUxDQWljM1ZpTFdNdE9EaGlPV1JpWVdJdE1qQm1NUzAwT0dRMExUaGtaak10T1dKbVlXSmlNREJqTUdJMEx6Qk5VakV0ZWpKM01HNVRTbGw0ZDBWNU56UndOVkZxVmpnMVZHMW5Ua0pMVUhKV056RjBOVFZPVkRBdlpqRXpNbVpsWkRndE1EUmhOQzAwTXpZMUxUZ3pOMkl0TjJaa05qVmpaV0psWVRGa0wydHBibWRmWVhKMGFIVnlMblI0ZENKZExBb0pDVnNpWTI5dWRHVnVkQzFzWlc1bmRHZ3RjbUZ1WjJVaUxDQXdMQ0ExTWpReU9EZ3dYU3dLQ1FsYkluTjBZWEowY3kxM2FYUm9JaXdnSWlSRGIyNTBaVzUwTFZSNWNHVWlMQ0FpSWwwc0Nna0pleUo0TFdGdGVpMWpjbVZrWlc1MGFXRnNJam9nSWtGTFNVRlpOMEZWTmtkUlJGWTFURU5RVmtWWUx6SXdNak14TURBMEwyVjFMV05sYm5SeVlXd3RNUzl6TXk5aGQzTTBYM0psY1hWbGMzUWlmU3dLQ1FsN0luZ3RZVzE2TFhObFkzVnlhWFI1TFhSdmEyVnVJam9nSWlKOUxBb0pDWHNpZUMxaGJYb3RZV3huYjNKcGRHaHRJam9nSWtGWFV6UXRTRTFCUXkxVFNFRXlOVFlpZlN3S0NRbDdJbmd0WVcxNkxXUmhkR1VpT2lBaU1qQXlNekV3TURSVU1qRXhPVEk0V2lJZ2ZRb0pYUXA5Q2c9PZRoNk2IA3ViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCFmb3JtLWRhdGE7IG5hbWU9IlgtQW16LVNpZ25hdHVyZSKUhpRoMIwCNjSUhpRlhZRSlGgdQ0BlMDc1ZWFlYzMyOTAxODUzMjc4ZGJjYWYyY2UyYjU2NDQzMzRlYWJlM2U3NTlmOTgzZjBmYTVjMzAwZWFjNGQ1lGg2S0B1Ymg3aDeHlGggjAxCeXRlc1BheWxvYWSUk5QpgZR9lChoDU5oDk5oD2gSXZQoaBiMGGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbZSGlGgrjDJmb3JtLWRhdGE7IG5hbWU9ImZpbGUiOyBmaWxlbmFtZT0ia2luZ19hcnRodXIudHh0IpSGlGgwjAI0OJSGlGWFlFKUaB1DMGtuaWdodHNvZm5pMTIzNDW14t4QCs6WdH0SFmq7YGusgc6K7eq49dcTVs5nQBRof5RoNkswdWJoN2g3h5RldWKMB19maWVsZHOUXZQoaBCMCU11bHRpRGljdJSTlF2UjARuYW1llIwHdGFnZ2luZ5SGlGGFlFKUfZRoGGgnc4xZPFRhZ2dpbmc+PFRhZ1NldD48VGFnPjxLZXk+T2JqZWN0VFRMSW5EYXlzPC9LZXk+PFZhbHVlPjE8L1ZhbHVlPjwvVGFnPjwvVGFnU2V0PjwvVGFnZ2luZz6Uh5Roq12UaK2MA2tleZSGlGGFlFKUfZRoGGgnc4yLc3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvZjEzMmZlZDgtMDRhNC00MzY1LTgzN2ItN2ZkNjVjZWJlYTFkL2tpbmdfYXJ0aHVyLnR4dJSHlGirXZRorYwMQ29udGVudC1UeXBllIaUYYWUUpR9lGgYaCdzjBl0ZXh0L3BsYWluOyBjaGFyc2V0PXV0Zi04lIeUaKtdlGitjBBYLUFtei1DcmVkZW50aWFslIaUYYWUUpR9lGgYaCdzjDpBS0lBWTdBVTZHUURWNUxDUFZFWC8yMDIzMTAwNC9ldS1jZW50cmFsLTEvczMvYXdzNF9yZXF1ZXN0lIeUaKtdlGitjBRYLUFtei1TZWN1cml0eS1Ub2tlbpSGlGGFlFKUfZRoGGgnc2g3h5Roq12UaK2MD1gtQW16LUFsZ29yaXRobZSGlGGFlFKUfZRoGGgnc4wQQVdTNC1ITUFDLVNIQTI1NpSHlGirXZRorYwKWC1BbXotRGF0ZZSGlGGFlFKUfZRoGGgnc4wQMjAyMzEwMDRUMjExOTI4WpSHlGirXZRorYwGUG9saWN5lIaUYYWUUpR9lGgYaCdzWIgDAABDbnNLQ1NKbGVIQnBjbUYwYVc5dUlqb2dJakl3TWpNdE1UQXRNRFJVTWpFNk1UazZNamhhSWl3S0NTSmpiMjVrYVhScGIyNXpJam9nV3dvSkNYc2lZblZqYTJWMElqb2dJbkIxWW01MVlpMXRibVZ0YjNONWJtVXRabWxzWlhNdFpYVXRZMlZ1ZEhKaGJDMHhMWEJ5WkNKOUxBb0pDVnNpWlhFaUxDQWlKSFJoWjJkcGJtY2lMQ0FpUEZSaFoyZHBibWMrUEZSaFoxTmxkRDQ4VkdGblBqeExaWGsrVDJKcVpXTjBWRlJNU1c1RVlYbHpQQzlMWlhrK1BGWmhiSFZsUGpFOEwxWmhiSFZsUGp3dlZHRm5Qand2VkdGblUyVjBQand2VkdGbloybHVaejRpWFN3S0NRbGJJbVZ4SWl3Z0lpUnJaWGtpTENBaWMzVmlMV010T0RoaU9XUmlZV0l0TWpCbU1TMDBPR1EwTFRoa1pqTXRPV0ptWVdKaU1EQmpNR0kwTHpCTlVqRXRlakozTUc1VFNsbDRkMFY1TnpSd05WRnFWamcxVkcxblRrSkxVSEpXTnpGME5UVk9WREF2WmpFek1tWmxaRGd0TURSaE5DMDBNelkxTFRnek4ySXROMlprTmpWalpXSmxZVEZrTDJ0cGJtZGZZWEowYUhWeUxuUjRkQ0pkTEFvSkNWc2lZMjl1ZEdWdWRDMXNaVzVuZEdndGNtRnVaMlVpTENBd0xDQTFNalF5T0Rnd1hTd0tDUWxiSW5OMFlYSjBjeTEzYVhSb0lpd2dJaVJEYjI1MFpXNTBMVlI1Y0dVaUxDQWlJbDBzQ2drSmV5SjRMV0Z0ZWkxamNtVmtaVzUwYVdGc0lqb2dJa0ZMU1VGWk4wRlZOa2RSUkZZMVRFTlFWa1ZZTHpJd01qTXhNREEwTDJWMUxXTmxiblJ5WVd3dE1TOXpNeTloZDNNMFgzSmxjWFZsYzNRaWZTd0tDUWw3SW5ndFlXMTZMWE5sWTNWeWFYUjVMWFJ2YTJWdUlqb2dJaUo5TEFvSkNYc2llQzFoYlhvdFlXeG5iM0pwZEdodElqb2dJa0ZYVXpRdFNFMUJReTFUU0VFeU5UWWlmU3dLQ1FsN0luZ3RZVzE2TFdSaGRHVWlPaUFpTWpBeU16RXdNRFJVTWpFeE9USTRXaUlnZlFvSlhRcDlDZz09lIeUaKtdlGitjA9YLUFtei1TaWduYXR1cmWUhpRhhZRSlH2UaBhoJ3OMQGUwNzVlYWVjMzI5MDE4NTMyNzhkYmNhZjJjZTJiNTY0NDMzNGVhYmUzZTc1OWY5ODNmMGZhNWMzMDBlYWM0ZDWUh5Roq12UKGitjARmaWxllIaUjAhmaWxlbmFtZZSMD2tpbmdfYXJ0aHVyLnR4dJSGlGWFlFKUfZRoGGiec2imh5RljA1faXNfbXVsdGlwYXJ0lIiMDV9pc19wcm9jZXNzZWSUiIwNX3F1b3RlX2ZpZWxkc5SIjAhfY2hhcnNldJROdWIu" + "pickle": "gASVQBIAAAAAAACMEGFpb2h0dHAuZm9ybWRhdGGUjAhGb3JtRGF0YZSTlCmBlH2UKIwHX3dyaXRlcpSMEWFpb2h0dHAubXVsdGlwYXJ0lIwPTXVsdGlwYXJ0V3JpdGVylJOUKYGUfZQojAlfYm91bmRhcnmUQyA3MjYyYWJjMzY3ZmM0ZGYzOTk0MGQ3ZmI5N2M4ZjBmZZSMCV9lbmNvZGluZ5ROjAlfZmlsZW5hbWWUTowIX2hlYWRlcnOUjBRtdWx0aWRpY3QuX211bHRpZGljdJSMC0NJTXVsdGlEaWN0lJOUXZRoEIwEaXN0cpSTlIwMQ29udGVudC1UeXBllIWUgZSMPm11bHRpcGFydC9mb3JtLWRhdGE7IGJvdW5kYXJ5PTcyNjJhYmMzNjdmYzRkZjM5OTQwZDdmYjk3YzhmMGZllIaUYYWUUpSMBl92YWx1ZZROjAZfcGFydHOUXZQojA9haW9odHRwLnBheWxvYWSUjA1TdHJpbmdQYXlsb2FklJOUKYGUfZQoaA2MBXV0Zi04lGgOTmgPaBJdlChoGIwTbXVsdGlwYXJ0L2Zvcm0tZGF0YZSGlGgVjBNDb250ZW50LURpc3Bvc2l0aW9ulIWUgZSMGWZvcm0tZGF0YTsgbmFtZT0idGFnZ2luZyKUhpRoFYwOQ29udGVudC1MZW5ndGiUhZSBlIwCODmUhpRlhZRSlGgdQ1k8VGFnZ2luZz48VGFnU2V0PjxUYWc+PEtleT5PYmplY3RUVExJbkRheXM8L0tleT48VmFsdWU+MTwvVmFsdWU+PC9UYWc+PC9UYWdTZXQ+PC9UYWdnaW5nPpSMBV9zaXpllEtZdWKMAJRoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjBVmb3JtLWRhdGE7IG5hbWU9ImtleSKUhpRoMIwDMTM5lIaUZYWUUpRoHUOLc3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvNGNlZTk3OWUtOThhNi00MDE5LTgzZjktYTg1MDZlNzMzM2U5L2tpbmdfYXJ0aHVyLnR4dJRoNkuLdWJoN2g3h5RoIimBlH2UKGgNaCVoDk5oD2gSXZQoaBhoJ4aUaCuMHmZvcm0tZGF0YTsgbmFtZT0iQ29udGVudC1UeXBlIpSGlGgwjAIyNZSGlGWFlFKUaB1DGXRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTiUaDZLGXViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCJmb3JtLWRhdGE7IG5hbWU9IlgtQW16LUNyZWRlbnRpYWwilIaUaDCMAjU4lIaUZYWUUpRoHUM6QUtJQVk3QVU2R1FEVjVMQ1BWRVgvMjAyNDAzMjcvZXUtY2VudHJhbC0xL3MzL2F3czRfcmVxdWVzdJRoNks6dWJoN2g3h5RoIimBlH2UKGgNaCVoDk5oD2gSXZQoaBhoJ4aUaCuMJmZvcm0tZGF0YTsgbmFtZT0iWC1BbXotU2VjdXJpdHktVG9rZW4ilIaUaDCMATCUhpRlhZRSlGgdQwCUaDZLAHViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCFmb3JtLWRhdGE7IG5hbWU9IlgtQW16LUFsZ29yaXRobSKUhpRoMIwCMTaUhpRlhZRSlGgdQxBBV1M0LUhNQUMtU0hBMjU2lGg2SxB1Ymg3aDeHlGgiKYGUfZQoaA1oJWgOTmgPaBJdlChoGGgnhpRoK4wcZm9ybS1kYXRhOyBuYW1lPSJYLUFtei1EYXRlIpSGlGgwjAIxNpSGlGWFlFKUaB1DEDIwMjQwMzI3VDE0MTYxNlqUaDZLEHViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjBhmb3JtLWRhdGE7IG5hbWU9IlBvbGljeSKUhpRoMIwDOTA0lIaUZYWUUpRoHUKIAwAAQ25zS0NTSmxlSEJwY21GMGFXOXVJam9nSWpJd01qUXRNRE10TWpkVU1UUTZNVFk2TVRaYUlpd0tDU0pqYjI1a2FYUnBiMjV6SWpvZ1d3b0pDWHNpWW5WamEyVjBJam9nSW5CMVltNTFZaTF0Ym1WdGIzTjVibVV0Wm1sc1pYTXRaWFV0WTJWdWRISmhiQzB4TFhCeVpDSjlMQW9KQ1ZzaVpYRWlMQ0FpSkhSaFoyZHBibWNpTENBaVBGUmhaMmRwYm1jK1BGUmhaMU5sZEQ0OFZHRm5QanhMWlhrK1QySnFaV04wVkZSTVNXNUVZWGx6UEM5TFpYaytQRlpoYkhWbFBqRThMMVpoYkhWbFBqd3ZWR0ZuUGp3dlZHRm5VMlYwUGp3dlZHRm5aMmx1Wno0aVhTd0tDUWxiSW1WeElpd2dJaVJyWlhraUxDQWljM1ZpTFdNdE9EaGlPV1JpWVdJdE1qQm1NUzAwT0dRMExUaGtaak10T1dKbVlXSmlNREJqTUdJMEx6Qk5VakV0ZWpKM01HNVRTbGw0ZDBWNU56UndOVkZxVmpnMVZHMW5Ua0pMVUhKV056RjBOVFZPVkRBdk5HTmxaVGszT1dVdE9UaGhOaTAwTURFNUxUZ3paamt0WVRnMU1EWmxOek16TTJVNUwydHBibWRmWVhKMGFIVnlMblI0ZENKZExBb0pDVnNpWTI5dWRHVnVkQzFzWlc1bmRHZ3RjbUZ1WjJVaUxDQXdMQ0ExTWpReU9EZ3dYU3dLQ1FsYkluTjBZWEowY3kxM2FYUm9JaXdnSWlSRGIyNTBaVzUwTFZSNWNHVWlMQ0FpSWwwc0Nna0pleUo0TFdGdGVpMWpjbVZrWlc1MGFXRnNJam9nSWtGTFNVRlpOMEZWTmtkUlJGWTFURU5RVmtWWUx6SXdNalF3TXpJM0wyVjFMV05sYm5SeVlXd3RNUzl6TXk5aGQzTTBYM0psY1hWbGMzUWlmU3dLQ1FsN0luZ3RZVzE2TFhObFkzVnlhWFI1TFhSdmEyVnVJam9nSWlKOUxBb0pDWHNpZUMxaGJYb3RZV3huYjNKcGRHaHRJam9nSWtGWFV6UXRTRTFCUXkxVFNFRXlOVFlpZlN3S0NRbDdJbmd0WVcxNkxXUmhkR1VpT2lBaU1qQXlOREF6TWpkVU1UUXhOakUyV2lJZ2ZRb0pYUXA5Q2c9PZRoNk2IA3ViaDdoN4eUaCIpgZR9lChoDWglaA5OaA9oEl2UKGgYaCeGlGgrjCFmb3JtLWRhdGE7IG5hbWU9IlgtQW16LVNpZ25hdHVyZSKUhpRoMIwCNjSUhpRlhZRSlGgdQ0AyYjRjNzdiMmJmZGQwOGJmODNiNWJiNjQyZDRiMDA2MmRhMTlmMDRlMDlmYjdiNWMxYjg1NmMyZDhkMTZkOTU2lGg2S0B1Ymg3aDeHlGggjAxCeXRlc1BheWxvYWSUk5QpgZR9lChoDU5oDk5oD2gSXZQoaBiMGGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbZSGlGgrjDJmb3JtLWRhdGE7IG5hbWU9ImZpbGUiOyBmaWxlbmFtZT0ia2luZ19hcnRodXIudHh0IpSGlGgwjAI0OJSGlGWFlFKUaB1DMGtuaWdodHNvZm5pMTIzNDW14t4QCs6WdH0SFmq7YGusgc6K7eq49dcTVs5nQBRof5RoNkswdWJoN2g3h5RldWKMB19maWVsZHOUXZQoaBCMCU11bHRpRGljdJSTlF2UjARuYW1llIwHdGFnZ2luZ5SGlGGFlFKUfZRoGGgnc4xZPFRhZ2dpbmc+PFRhZ1NldD48VGFnPjxLZXk+T2JqZWN0VFRMSW5EYXlzPC9LZXk+PFZhbHVlPjE8L1ZhbHVlPjwvVGFnPjwvVGFnU2V0PjwvVGFnZ2luZz6Uh5Roq12UaK2MA2tleZSGlGGFlFKUfZRoGGgnc4yLc3ViLWMtODhiOWRiYWItMjBmMS00OGQ0LThkZjMtOWJmYWJiMDBjMGI0LzBNUjEtejJ3MG5TSll4d0V5NzRwNVFqVjg1VG1nTkJLUHJWNzF0NTVOVDAvNGNlZTk3OWUtOThhNi00MDE5LTgzZjktYTg1MDZlNzMzM2U5L2tpbmdfYXJ0aHVyLnR4dJSHlGirXZRorYwMQ29udGVudC1UeXBllIaUYYWUUpR9lGgYaCdzjBl0ZXh0L3BsYWluOyBjaGFyc2V0PXV0Zi04lIeUaKtdlGitjBBYLUFtei1DcmVkZW50aWFslIaUYYWUUpR9lGgYaCdzjDpBS0lBWTdBVTZHUURWNUxDUFZFWC8yMDI0MDMyNy9ldS1jZW50cmFsLTEvczMvYXdzNF9yZXF1ZXN0lIeUaKtdlGitjBRYLUFtei1TZWN1cml0eS1Ub2tlbpSGlGGFlFKUfZRoGGgnc2g3h5Roq12UaK2MD1gtQW16LUFsZ29yaXRobZSGlGGFlFKUfZRoGGgnc4wQQVdTNC1ITUFDLVNIQTI1NpSHlGirXZRorYwKWC1BbXotRGF0ZZSGlGGFlFKUfZRoGGgnc4wQMjAyNDAzMjdUMTQxNjE2WpSHlGirXZRorYwGUG9saWN5lIaUYYWUUpR9lGgYaCdzWIgDAABDbnNLQ1NKbGVIQnBjbUYwYVc5dUlqb2dJakl3TWpRdE1ETXRNamRVTVRRNk1UWTZNVFphSWl3S0NTSmpiMjVrYVhScGIyNXpJam9nV3dvSkNYc2lZblZqYTJWMElqb2dJbkIxWW01MVlpMXRibVZ0YjNONWJtVXRabWxzWlhNdFpYVXRZMlZ1ZEhKaGJDMHhMWEJ5WkNKOUxBb0pDVnNpWlhFaUxDQWlKSFJoWjJkcGJtY2lMQ0FpUEZSaFoyZHBibWMrUEZSaFoxTmxkRDQ4VkdGblBqeExaWGsrVDJKcVpXTjBWRlJNU1c1RVlYbHpQQzlMWlhrK1BGWmhiSFZsUGpFOEwxWmhiSFZsUGp3dlZHRm5Qand2VkdGblUyVjBQand2VkdGbloybHVaejRpWFN3S0NRbGJJbVZ4SWl3Z0lpUnJaWGtpTENBaWMzVmlMV010T0RoaU9XUmlZV0l0TWpCbU1TMDBPR1EwTFRoa1pqTXRPV0ptWVdKaU1EQmpNR0kwTHpCTlVqRXRlakozTUc1VFNsbDRkMFY1TnpSd05WRnFWamcxVkcxblRrSkxVSEpXTnpGME5UVk9WREF2TkdObFpUazNPV1V0T1RoaE5pMDBNREU1TFRnelpqa3RZVGcxTURabE56TXpNMlU1TDJ0cGJtZGZZWEowYUhWeUxuUjRkQ0pkTEFvSkNWc2lZMjl1ZEdWdWRDMXNaVzVuZEdndGNtRnVaMlVpTENBd0xDQTFNalF5T0Rnd1hTd0tDUWxiSW5OMFlYSjBjeTEzYVhSb0lpd2dJaVJEYjI1MFpXNTBMVlI1Y0dVaUxDQWlJbDBzQ2drSmV5SjRMV0Z0ZWkxamNtVmtaVzUwYVdGc0lqb2dJa0ZMU1VGWk4wRlZOa2RSUkZZMVRFTlFWa1ZZTHpJd01qUXdNekkzTDJWMUxXTmxiblJ5WVd3dE1TOXpNeTloZDNNMFgzSmxjWFZsYzNRaWZTd0tDUWw3SW5ndFlXMTZMWE5sWTNWeWFYUjVMWFJ2YTJWdUlqb2dJaUo5TEFvSkNYc2llQzFoYlhvdFlXeG5iM0pwZEdodElqb2dJa0ZYVXpRdFNFMUJReTFUU0VFeU5UWWlmU3dLQ1FsN0luZ3RZVzE2TFdSaGRHVWlPaUFpTWpBeU5EQXpNamRVTVRReE5qRTJXaUlnZlFvSlhRcDlDZz09lIeUaKtdlGitjA9YLUFtei1TaWduYXR1cmWUhpRhhZRSlH2UaBhoJ3OMQDJiNGM3N2IyYmZkZDA4YmY4M2I1YmI2NDJkNGIwMDYyZGExOWYwNGUwOWZiN2I1YzFiODU2YzJkOGQxNmQ5NTaUh5Roq12UKGitjARmaWxllIaUjAhmaWxlbmFtZZSMD2tpbmdfYXJ0aHVyLnR4dJSGlGWFlFKUfZRoGGiec2imh5RljA1faXNfbXVsdGlwYXJ0lIiMDV9pc19wcm9jZXNzZWSUiIwNX3F1b3RlX2ZpZWxkc5SIjAhfY2hhcnNldJROdWIu" }, "headers": { "User-Agent": [ - "PubNub-Python-Asyncio/7.2.0" + "PubNub-Python-Asyncio/7.4.2" ] } }, @@ -62,16 +62,16 @@ }, "headers": { "x-amz-id-2": [ - "2gGUgbJAn+pzGn9T3bO1wIVjQaMbYXRrybOZRVa1fNhLuTEN8ygN5oAY0fU1wBknhnZJNWMMP+E=" + "sLfBX7SyW1G9k55Z0mYBFPxhudkF9Qz9/y4XDxSMpLIMyJXRYRp3S3XveE9no3xX3T+Hi45AXh25iocM3rWjUQ==" ], "x-amz-request-id": [ - "1M1MCS17TAQ0VXC4" + "W4CR5WKB0MKJ20FJ" ], "Date": [ - "Wed, 04 Oct 2023 21:18:29 GMT" + "Wed, 27 Mar 2024 14:15:17 GMT" ], "x-amz-expiration": [ - "expiry-date=\"Fri, 06 Oct 2023 00:00:00 GMT\", rule-id=\"Archive file 1 day after creation\"" + "expiry-date=\"Fri, 29 Mar 2024 00:00:00 GMT\", rule-id=\"Archive file 1 day after creation\"" ], "x-amz-server-side-encryption": [ "AES256" @@ -80,7 +80,7 @@ "\"54c0565f0dd787c6d22c3d455b12d6ac\"" ], "Location": [ - "https://pubnub-mnemosyne-files-eu-central-1-prd.s3.eu-central-1.amazonaws.com/{PN_KEY_SUBSCRIBE}%2F0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0%2Ff132fed8-04a4-4365-837b-7fd65cebea1d%2Fking_arthur.txt" + "https://pubnub-mnemosyne-files-eu-central-1-prd.s3.eu-central-1.amazonaws.com/{PN_KEY_SUBSCRIBE}%2F0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0%2F4cee979e-98a6-4019-83f9-a8506e7333e9%2Fking_arthur.txt" ], "Server": [ "AmazonS3" @@ -94,11 +94,11 @@ { "request": { "method": "GET", - "uri": "https://ps.pndsn.com/v1/files/publish-file/{PN_KEY_PUBLISH}/{PN_KEY_SUBSCRIBE}/0/files_asyncio_ch/0/%22a25pZ2h0c29mbmkxMjM0NXmhf%2BORk1GxlwqjcrSxSR7QjuwQHs4oHPiUsXidPQkk1vPPyxRJDAK7XvCHEfoIKw5pj2GzXG55ibJWigH5EujGk8%2Bvc%2FGvZsjf7h7qFTCVjGmvezDRlIEZANrQgOyEct4%2FoatL3TTnOQ%2FbUymrAlwAvm8DxdbRi6wmHt1%2FxvWJ%22?meta=null&store=1&ttl=222", + "uri": "https://ps.pndsn.com/v1/files/publish-file/{PN_KEY_PUBLISH}/{PN_KEY_SUBSCRIBE}/0/files_asyncio_ch/0/%22a25pZ2h0c29mbmkxMjM0NXmhf%2BORk1GxlwqjcrSxSR7QjuwQHs4oHPiUsXidPQkk1vPPyxRJDAK7XvCHEfoIK%2FRZQp7A%2BLcccQ7uFhyz1B%2BH07cIalE%2F6KNNxUx40Y0a57VZsd6%2BAXuhmCuggimMsgCIxXIR5RWpZBBETdr8VBBDrQz0gGmCFgPp6%2Fji%2BQLO%22?meta=null&store=1&ttl=222", "body": null, "headers": { "User-Agent": [ - "PubNub-Python-Asyncio/7.2.0" + "PubNub-Python-Asyncio/7.4.2" ] } }, @@ -109,7 +109,7 @@ }, "headers": { "Date": [ - "Wed, 04 Oct 2023 21:18:28 GMT" + "Wed, 27 Mar 2024 14:15:16 GMT" ], "Content-Type": [ "text/javascript; charset=\"UTF-8\"" @@ -131,18 +131,18 @@ ] }, "body": { - "string": "[1,\"Sent\",\"16964543088558241\"]" + "string": "[1,\"Sent\",\"17115489163320100\"]" } } }, { "request": { "method": "GET", - "uri": "https://ps.pndsn.com/v1/files/{PN_KEY_SUBSCRIBE}/channels/files_asyncio_ch/files/f132fed8-04a4-4365-837b-7fd65cebea1d/king_arthur.txt", + "uri": "https://ps.pndsn.com/v1/files/{PN_KEY_SUBSCRIBE}/channels/files_asyncio_ch/files/4cee979e-98a6-4019-83f9-a8506e7333e9/king_arthur.txt", "body": null, "headers": { "User-Agent": [ - "PubNub-Python-Asyncio/7.2.0" + "PubNub-Python-Asyncio/7.4.2" ] } }, @@ -153,7 +153,7 @@ }, "headers": { "Date": [ - "Wed, 04 Oct 2023 21:18:28 GMT" + "Wed, 27 Mar 2024 14:15:16 GMT" ], "Content-Length": [ "0" @@ -165,10 +165,10 @@ "*" ], "Cache-Control": [ - "public, max-age=2732, immutable" + "public, max-age=2924, immutable" ], "Location": [ - "https://files-eu-central-1.pndsn.com/{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/f132fed8-04a4-4365-837b-7fd65cebea1d/king_arthur.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAY7AU6GQDV5LCPVEX%2F20231004%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20231004T210000Z&X-Amz-Expires=3900&X-Amz-SignedHeaders=host&X-Amz-Signature=283480846ee74d2ae55b15f6e697c23e30e7ae5069e7dda2dfe2196d108447a3" + "https://files-eu-central-1.pndsn.com/{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/4cee979e-98a6-4019-83f9-a8506e7333e9/king_arthur.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAY7AU6GQDV5LCPVEX%2F20240327%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20240327T140000Z&X-Amz-Expires=3900&X-Amz-SignedHeaders=host&X-Amz-Signature=337cf3bf979ff66c54a9b499ca706ae0b63d0c78518889d304efcc9e25a7c9c1" ] }, "body": { @@ -179,11 +179,11 @@ { "request": { "method": "GET", - "uri": "https://files-eu-central-1.pndsn.com/{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/f132fed8-04a4-4365-837b-7fd65cebea1d/king_arthur.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAY7AU6GQDV5LCPVEX%2F20231004%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20231004T210000Z&X-Amz-Expires=3900&X-Amz-Signature=283480846ee74d2ae55b15f6e697c23e30e7ae5069e7dda2dfe2196d108447a3&X-Amz-SignedHeaders=host", + "uri": "https://files-eu-central-1.pndsn.com/{PN_KEY_SUBSCRIBE}/0MR1-z2w0nSJYxwEy74p5QjV85TmgNBKPrV71t55NT0/4cee979e-98a6-4019-83f9-a8506e7333e9/king_arthur.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAY7AU6GQDV5LCPVEX%2F20240327%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20240327T140000Z&X-Amz-Expires=3900&X-Amz-Signature=337cf3bf979ff66c54a9b499ca706ae0b63d0c78518889d304efcc9e25a7c9c1&X-Amz-SignedHeaders=host", "body": null, "headers": { "User-Agent": [ - "PubNub-Python-Asyncio/7.2.0" + "PubNub-Python-Asyncio/7.4.2" ] } }, @@ -203,13 +203,13 @@ "keep-alive" ], "Date": [ - "Wed, 04 Oct 2023 21:18:30 GMT" + "Wed, 27 Mar 2024 14:15:17 GMT" ], "Last-Modified": [ - "Wed, 04 Oct 2023 21:18:29 GMT" + "Wed, 27 Mar 2024 14:15:17 GMT" ], "x-amz-expiration": [ - "expiry-date=\"Fri, 06 Oct 2023 00:00:00 GMT\", rule-id=\"Archive file 1 day after creation\"" + "expiry-date=\"Fri, 29 Mar 2024 00:00:00 GMT\", rule-id=\"Archive file 1 day after creation\"" ], "Etag": [ "\"54c0565f0dd787c6d22c3d455b12d6ac\"" @@ -227,13 +227,13 @@ "Miss from cloudfront" ], "Via": [ - "1.1 7135e74802b850169bf88eb66663d5a6.cloudfront.net (CloudFront)" + "1.1 51ef96adddea56ccd77a68113e740792.cloudfront.net (CloudFront)" ], "X-Amz-Cf-Pop": [ - "WAW51-P3" + "HAM50-P3" ], "X-Amz-Cf-Id": [ - "u-rpBgX3rEdd-62IVkAqx-eTupjgGMy9iiKSbeCcLC5brTJ8IePgJw==" + "k-y4MUu4bX9-Ii1rYUfV7gMhU-NvxnR-4bLhA70SWiNeEAIAh_lb6g==" ] }, "body": { diff --git a/tests/integrational/native_threads/test_here_now.py b/tests/integrational/native_threads/test_here_now.py index 1e43f58d..97536b82 100644 --- a/tests/integrational/native_threads/test_here_now.py +++ b/tests/integrational/native_threads/test_here_now.py @@ -2,7 +2,6 @@ import logging import time -import pytest import pubnub import threading @@ -22,7 +21,6 @@ def callback(self, response, status): self.status = status self.event.set() - @pytest.mark.skip(reason="Needs to be reworked to use VCR") def test_single_channel(self): pubnub = PubNub(pnconf_sub_copy()) ch = helper.gen_channel("herenow-asyncio-channel") @@ -58,7 +56,6 @@ def test_single_channel(self): pubnub.stop() - @pytest.mark.skip(reason="Needs to be reworked to use VCR") def test_multiple_channels(self): pubnub = PubNub(pnconf_sub_copy()) ch1 = helper.gen_channel("here-now-native-sync-ch1") diff --git a/tests/integrational/native_threads/test_subscribe.py b/tests/integrational/native_threads/test_subscribe.py index f23c6262..4b0280ff 100644 --- a/tests/integrational/native_threads/test_subscribe.py +++ b/tests/integrational/native_threads/test_subscribe.py @@ -1,7 +1,6 @@ import binascii import logging import unittest -import time import pubnub as pn from pubnub.exceptions import PubNubException @@ -209,46 +208,56 @@ def test_subscribe_cg_publish_unsubscribe(self): def test_subscribe_cg_join_leave(self): ch = helper.gen_channel("test-subscribe-unsubscribe-channel") gr = helper.gen_channel("test-subscribe-unsubscribe-group") - pubnub = PubNub(pnconf_env_copy(enable_subscribe=True, daemon=True)) pubnub_listener = PubNub(pnconf_env_copy(enable_subscribe=True, daemon=True)) - non_subscribe_listener = NonSubscribeListener() + callback_messages = SubscribeListener() + callback_presence = SubscribeListener() - pubnub.add_channel_to_channel_group() \ + result = pubnub.add_channel_to_channel_group() \ .channel_group(gr) \ .channels(ch) \ - .pn_async(non_subscribe_listener.callback) - result = non_subscribe_listener.await_result_and_reset() - assert isinstance(result, PNChannelGroupsAddChannelResult) + .sync() - time.sleep(1) + assert isinstance(result.result, PNChannelGroupsAddChannelResult) - callback_presence = SubscribeListener() + pubnub.config.uuid = helper.gen_channel("messenger") + pubnub_listener.config.uuid = helper.gen_channel("listener") + pubnub.add_listener(callback_messages) pubnub_listener.add_listener(callback_presence) + pubnub_listener.subscribe().channel_groups(gr).with_presence().execute() callback_presence.wait_for_connect() - prs_envelope = callback_presence.wait_for_presence_on(ch) - assert prs_envelope.event == 'join' - assert prs_envelope.uuid == pubnub_listener.uuid - assert prs_envelope.channel == ch - assert prs_envelope.subscription == gr + envelope = callback_presence.wait_for_presence_on(ch) + assert envelope.channel == ch + assert envelope.event == 'join' + assert envelope.uuid == pubnub_listener.uuid - prs_envelope = callback_presence.wait_for_presence_on(ch) - pubnub_listener.unsubscribe().channel_groups(gr).execute() + pubnub.subscribe().channel_groups(gr).execute() + callback_messages.wait_for_connect() - assert prs_envelope.event == 'leave' - assert prs_envelope.uuid == pubnub.uuid - assert prs_envelope.channel == ch - assert prs_envelope.subscription == gr + envelope = callback_presence.wait_for_presence_on(ch) + assert envelope.channel == ch + assert envelope.event == 'join' + assert envelope.uuid == pubnub.uuid - pubnub.remove_channel_from_channel_group() \ + pubnub.unsubscribe().channel_groups(gr).execute() + callback_messages.wait_for_disconnect() + + envelope = callback_presence.wait_for_presence_on(ch) + assert envelope.channel == ch + assert envelope.event == 'leave' + assert envelope.uuid == pubnub.uuid + + pubnub_listener.unsubscribe().channel_groups(gr).execute() + callback_presence.wait_for_disconnect() + + result = pubnub.remove_channel_from_channel_group() \ .channel_group(gr) \ .channels(ch) \ - .pn_async(non_subscribe_listener.callback) - result = non_subscribe_listener.await_result_and_reset() - assert isinstance(result, PNChannelGroupsRemoveChannelResult) + .sync() + assert isinstance(result.result, PNChannelGroupsRemoveChannelResult) pubnub.stop() pubnub_listener.stop() diff --git a/tests/integrational/vcr_asyncio_sleeper.py b/tests/integrational/vcr_asyncio_sleeper.py index 48cd98da..dd861b08 100644 --- a/tests/integrational/vcr_asyncio_sleeper.py +++ b/tests/integrational/vcr_asyncio_sleeper.py @@ -21,6 +21,8 @@ async def fake_sleeper(v): def decorate(f): @wraps(f) async def call(*args, event_loop=None): + if not event_loop: + event_loop = asyncio.get_event_loop() await f(*args, sleeper=(fake_sleeper if (len(cs) > 0) else asyncio.sleep), event_loop=event_loop) return call