From 5f715fdd73773522d70746c7e1f4bc34b8fc95a7 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 08:06:45 +0100 Subject: [PATCH 01/14] Add models --- pubnub/models/consumer/message_actions.py | 50 +++++++++++++++++++++++ pubnub/models/consumer/pubsub.py | 8 ++++ 2 files changed, 58 insertions(+) create mode 100644 pubnub/models/consumer/message_actions.py diff --git a/pubnub/models/consumer/message_actions.py b/pubnub/models/consumer/message_actions.py new file mode 100644 index 00000000..a8ee2b12 --- /dev/null +++ b/pubnub/models/consumer/message_actions.py @@ -0,0 +1,50 @@ +class PNMessageAction(object): + def __init__(self, message_action=None): + if message_action is not None: + self.type = message_action['type'] + self.value = message_action['value'] + self.message_timetoken = message_action['messageTimetoken'] + self.uuid = message_action['uuid'] + self.action_timetoken = message_action['actionTimetoken'] + else: + self.type = None + self.value = None + self.message_timetoken = None + self.uuid = None + self.action_timetoken = None + + def __str__(self): + return "Message action with tt: %s for uuid %s with value %s " % (self.action_timetoken, self.uuid, self.value) + + +class PNGetMessageActionsResult(object): + def __init__(self, result): + """ + Representation of get message actions server response + + :param result: result of get message actions operation + """ + self._result = result + self.actions = result['actions'] + + def __str__(self): + return "Get message actions success" + + +class PNAddMessageActionResult(PNMessageAction): + + def __init__(self, message_action): + super(PNAddMessageActionResult, self).__init__(message_action) + + +class PNRemoveMessageActionResult(object): + def __init__(self, result): + """s + Representation of remove message actions server response + + :param result: result of remove message actions operation + """ + self._result = result + + def __str__(self): + return "Remove message actions success" diff --git a/pubnub/models/consumer/pubsub.py b/pubnub/models/consumer/pubsub.py index 936ef3d0..5f536f1c 100644 --- a/pubnub/models/consumer/pubsub.py +++ b/pubnub/models/consumer/pubsub.py @@ -1,5 +1,7 @@ import six +from pubnub.models.consumer.message_actions import PNMessageAction + class PNMessageResult(object): def __init__(self, message, subscription, channel, timetoken, user_metadata=None, publisher=None): @@ -71,6 +73,12 @@ def __init__(self, event, uuid, timestamp, occupancy, subscription, channel, self.user_metadata = user_metadata +class PNMessageActionResult(PNMessageAction): + + def __init__(self, result): + super(PNMessageActionResult, self).__init__(result) + + class PNPublishResult(object): def __init__(self, envelope, timetoken): """ From 16af406dd111b01e083c26057d0a42a385d49cb5 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 08:09:38 +0100 Subject: [PATCH 02/14] Register endpoint operations --- pubnub/enums.py | 3 +++ pubnub/managers.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/pubnub/enums.py b/pubnub/enums.py index 6d7ef510..15fc27da 100644 --- a/pubnub/enums.py +++ b/pubnub/enums.py @@ -81,6 +81,9 @@ class PNOperationType(object): PNManageMembershipsOperation = 40 PNAccessManagerGrantToken = 41 + PNAddMessageAction = 42 + PNGetMessageActions = 43 + PNDeleteMessageAction = 44 class PNHeartbeatNotificationOptions(object): diff --git a/pubnub/managers.py b/pubnub/managers.py index 1c51cc26..08dd9054 100644 --- a/pubnub/managers.py +++ b/pubnub/managers.py @@ -484,6 +484,10 @@ def endpoint_name_for_operation(operation_type): PNOperationType.PNManageMembershipsOperation: 'obj', PNOperationType.PNAccessManagerGrantToken: 'pamv3', + + PNOperationType.PNAddMessageAction: 'msga', + PNOperationType.PNGetMessageActions: 'msga', + PNOperationType.PNDeleteMessageAction: 'msga' }[operation_type] return endpoint From 39cf1a3833da0932aa6057d09a348a8c9221d661 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 08:10:15 +0100 Subject: [PATCH 03/14] Register known validation errors --- pubnub/errors.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pubnub/errors.py b/pubnub/errors.py index 2f3eaa6d..6a6b246c 100644 --- a/pubnub/errors.py +++ b/pubnub/errors.py @@ -33,3 +33,11 @@ PNERR_INVALID_META = "Invalid meta parameter" PNERR_PERMISSION_MISSING = "Permission missing" PNERR_INVALID_ACCESS_TOKEN = "Invalid access token" +PNERR_MESSAGE_ACTION_MISSING = "Message action is missing" +PNERR_MESSAGE_ACTION_TYPE_MISSING = "Message action type is missing" +PNERR_MESSAGE_ACTION_VALUE_MISSING = "Message action value is missing" +PNERR_MESSAGE_TIMETOKEN_MISSING = "Message timetoken is missing" +PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING = "Message action timetoken is missing" +PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS = "History can return message action data for a single channel only. " \ + "Either pass a single channel or disable the includeMessageActions " \ + "flag. " From 996b2a6721c87133b844527ee4ea22d225abcaab Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 08:11:39 +0100 Subject: [PATCH 04/14] Implement Message Actions APIs. --- .../message_actions/add_message_action.py | 81 +++++++++++++++++ .../message_actions/get_message_actions.py | 87 +++++++++++++++++++ .../message_actions/remove_message_action.py | 75 ++++++++++++++++ pubnub/pubnub_core.py | 12 +++ 4 files changed, 255 insertions(+) create mode 100644 pubnub/endpoints/message_actions/add_message_action.py create mode 100644 pubnub/endpoints/message_actions/get_message_actions.py create mode 100644 pubnub/endpoints/message_actions/remove_message_action.py diff --git a/pubnub/endpoints/message_actions/add_message_action.py b/pubnub/endpoints/message_actions/add_message_action.py new file mode 100644 index 00000000..dd38aaad --- /dev/null +++ b/pubnub/endpoints/message_actions/add_message_action.py @@ -0,0 +1,81 @@ +from pubnub import utils +from pubnub.endpoints.endpoint import Endpoint +from pubnub.errors import PNERR_MESSAGE_ACTION_VALUE_MISSING, PNERR_MESSAGE_ACTION_TYPE_MISSING, \ + PNERR_MESSAGE_TIMETOKEN_MISSING, PNERR_MESSAGE_ACTION_MISSING +from pubnub.exceptions import PubNubException +from pubnub.enums import HttpMethod, PNOperationType +from pubnub.models.consumer.message_actions import PNAddMessageActionResult + + +class AddMessageAction(Endpoint): + ADD_MESSAGE_ACTION_PATH = "/v1/message-actions/%s/channel/%s/message/%s" + + def __init__(self, pubnub): + Endpoint.__init__(self, pubnub) + self._channel = None + self._message_action = None + + def channel(self, channel): + self._channel = str(channel) + return self + + def message_action(self, message_action): + self._message_action = message_action + return self + + def custom_params(self): + return {} + + def build_data(self): + params = { + 'type': self._message_action.type, + 'value': self._message_action.value + } + + return utils.write_value_as_string(params) + + def build_path(self): + return AddMessageAction.ADD_MESSAGE_ACTION_PATH % ( + self.pubnub.config.subscribe_key, + utils.url_encode(self._channel), + self._message_action.message_timetoken + ) + + def http_method(self): + return HttpMethod.POST + + def validate_params(self): + self.validate_subscribe_key() + self.validate_channel() + self.validate_message_action() + + def create_response(self, envelope): + return PNAddMessageActionResult(envelope['data']) + + def is_auth_required(self): + return True + + def request_timeout(self): + return self.pubnub.config.non_subscribe_request_timeout + + def connect_timeout(self): + return self.pubnub.config.connect_timeout + + def operation_type(self): + return PNOperationType.PNAddMessageAction + + def name(self): + return "Add message action" + + def validate_message_action(self): + if self._message_action is None: + raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_MISSING) + + if self._message_action.message_timetoken is None: + raise PubNubException(pn_error=PNERR_MESSAGE_TIMETOKEN_MISSING) + + if self._message_action.type is None or len(self._message_action.type) == 0: + raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_TYPE_MISSING) + + if self._message_action.value is None or len(self._message_action.value) == 0: + raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_VALUE_MISSING) diff --git a/pubnub/endpoints/message_actions/get_message_actions.py b/pubnub/endpoints/message_actions/get_message_actions.py new file mode 100644 index 00000000..da20e75b --- /dev/null +++ b/pubnub/endpoints/message_actions/get_message_actions.py @@ -0,0 +1,87 @@ +import six + +from pubnub import utils +from pubnub.endpoints.endpoint import Endpoint +from pubnub.models.consumer.message_actions import PNGetMessageActionsResult, PNMessageAction +from pubnub.enums import HttpMethod, PNOperationType + + +class GetMessageActions(Endpoint): + GET_MESSAGE_ACTIONS_PATH = '/v1/message-actions/%s/channel/%s' + MAX_LIMIT = 100 + + def __init__(self, pubnub): + Endpoint.__init__(self, pubnub) + self._channel = None + self._start = None + self._end = None + self._limit = GetMessageActions.MAX_LIMIT + + def channel(self, channel): + self._channel = str(channel) + return self + + def start(self, start): + assert isinstance(start, six.string_types) + self._start = start + return self + + def end(self, end): + assert isinstance(end, six.string_types) + self._end = end + return self + + def limit(self, limit): + assert isinstance(limit, six.integer_types) + self._limit = limit + return self + + def custom_params(self): + params = {} + + if self._start is not None: + params['start'] = self._start + + if self._end is not None and self._start is None: + params['end'] = self._end + + if self._limit != GetMessageActions.MAX_LIMIT: + params['limit'] = self._limit + + return params + + def build_path(self): + return GetMessageActions.GET_MESSAGE_ACTIONS_PATH % ( + self.pubnub.config.subscribe_key, + utils.url_encode(self._channel) + ) + + def http_method(self): + return HttpMethod.GET + + def is_auth_required(self): + return True + + def validate_params(self): + self.validate_subscribe_key() + self.validate_channel() + + def create_response(self, envelope): # pylint: disable=W0221 + result = envelope + result['actions'] = [] + for action in result['data']: + result['actions'].append(PNMessageAction(action)) + + return PNGetMessageActionsResult(result) + + def request_timeout(self): + return self.pubnub.config.non_subscribe_request_timeout + + def connect_timeout(self): + return self.pubnub.config.connect_timeout + + def operation_type(self): + return PNOperationType.PNGetMessageActions + + def name(self): + return 'Get message actions' diff --git a/pubnub/endpoints/message_actions/remove_message_action.py b/pubnub/endpoints/message_actions/remove_message_action.py new file mode 100644 index 00000000..ef594ec7 --- /dev/null +++ b/pubnub/endpoints/message_actions/remove_message_action.py @@ -0,0 +1,75 @@ +from pubnub import utils +from pubnub.endpoints.endpoint import Endpoint +from pubnub.errors import PNERR_MESSAGE_TIMETOKEN_MISSING, PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING +from pubnub.exceptions import PubNubException +from pubnub.enums import HttpMethod, PNOperationType + + +class RemoveMessageAction(Endpoint): + REMOVE_MESSAGE_ACTION_PATH = "/v1/message-actions/%s/channel/%s/message/%s/action/%s" + + def __init__(self, pubnub): + Endpoint.__init__(self, pubnub) + self._channel = None + self._message_timetoken = None + self._action_timetoken = None + + def channel(self, channel): + self._channel = str(channel) + return self + + def message_timetoken(self, message_timetoken): + self._message_timetoken = message_timetoken + return self + + def action_timetoken(self, action_timetoken): + self._action_timetoken = action_timetoken + return self + + def custom_params(self): + return {} + + def build_data(self): + return None + + def build_path(self): + return RemoveMessageAction.REMOVE_MESSAGE_ACTION_PATH % ( + self.pubnub.config.subscribe_key, + utils.url_encode(self._channel), + self._message_timetoken, + self._action_timetoken + ) + + def http_method(self): + return HttpMethod.DELETE + + def validate_params(self): + self.validate_subscribe_key() + self.validate_channel() + self.validate_timetokens() + + def create_response(self, envelope): + return {} + + def is_auth_required(self): + return True + + def request_timeout(self): + return self.pubnub.config.non_subscribe_request_timeout + + def connect_timeout(self): + return self.pubnub.config.connect_timeout + + def operation_type(self): + return PNOperationType.PNDeleteMessageAction + + def name(self): + return "Remove message action" + + def validate_timetokens(self): + + if self._message_timetoken is None: + raise PubNubException(pn_error=PNERR_MESSAGE_TIMETOKEN_MISSING) + + if self._action_timetoken is None: + raise PubNubException(pn_error=PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING) diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index 5da55ff2..6dd46c6d 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -3,6 +3,9 @@ from abc import ABCMeta, abstractmethod +from pubnub.endpoints.message_actions.add_message_action import AddMessageAction +from pubnub.endpoints.message_actions.get_message_actions import GetMessageActions +from pubnub.endpoints.message_actions.remove_message_action import RemoveMessageAction from .managers import BasePathManager, TokenManager, TokenManagerProperties from .builders import SubscribeBuilder from .builders import UnsubscribeBuilder @@ -230,6 +233,15 @@ def manage_members(self): def manage_memberships(self): return ManageMemberships(self) + def add_message_action(self): + return AddMessageAction(self) + + def get_message_actions(self): + return GetMessageActions(self) + + def remove_message_action(self): + return RemoveMessageAction(self) + def time(self): return Time(self) From dc1661092fe969bb16239da40e914548b80901f7 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 08:29:04 +0100 Subject: [PATCH 05/14] Extend the listener with Message Actions API callback --- pubnub/callbacks.py | 3 +++ pubnub/managers.py | 8 ++++---- pubnub/models/server/subscribe.py | 9 +++------ pubnub/workers.py | 20 ++++++++++++++++---- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/pubnub/callbacks.py b/pubnub/callbacks.py index 7bf4afb1..8445cea4 100644 --- a/pubnub/callbacks.py +++ b/pubnub/callbacks.py @@ -34,6 +34,9 @@ def space(self, pubnub, space): def membership(self, pubnub, membership): pass + def message_action(self, pubnub, message_action): + pass + class ReconnectionCallback(object): @abstractmethod diff --git a/pubnub/managers.py b/pubnub/managers.py index 08dd9054..93df8cb5 100644 --- a/pubnub/managers.py +++ b/pubnub/managers.py @@ -223,6 +223,10 @@ def announce_membership(self, membership): for callback in self._listeners: callback.membership(self._pubnub, membership) + def announce_message_action(self, message_action): + for callback in self._listeners: + callback.message_action(self._pubnub, message_action) + def announce_presence(self, presence): for callback in self._listeners: callback.presence(self._pubnub, presence) @@ -583,10 +587,6 @@ def get_token_by_match(self, tms_properties, match_type): return None def get_extended_resource_type(self, r_type_abbr): - if r_type_abbr == "chan": - return PNResourceType.CHANNEL - if r_type_abbr == "grp": - return PNResourceType.GROUP if r_type_abbr == "usr": return PNResourceType.USER if r_type_abbr == "spc": diff --git a/pubnub/models/server/subscribe.py b/pubnub/models/server/subscribe.py index e1ed6b67..9e85a280 100644 --- a/pubnub/models/server/subscribe.py +++ b/pubnub/models/server/subscribe.py @@ -32,8 +32,7 @@ def __init__(self): self.origination_timetoken = None self.publish_metadata = None self.only_channel_subscription = False - self.is_signal = False - self.is_object = False + self.type = 0 @classmethod def from_json(cls, json_input): @@ -51,10 +50,8 @@ def from_json(cls, json_input): if 'o' in json_input: message.origination_timetoken = json_input['o'] message.publish_metadata = PublishMetadata.from_json(json_input['p']) - if 'e' in json_input and json_input['e'] == 1: - message.is_signal = True - if 'e' in json_input and json_input['e'] == 2: - message.is_object = True + if 'e' in json_input: + message.type = json_input['e'] return message diff --git a/pubnub/workers.py b/pubnub/workers.py index 27a14ca5..51745e63 100644 --- a/pubnub/workers.py +++ b/pubnub/workers.py @@ -2,7 +2,7 @@ from abc import abstractmethod from .utils import strip_right -from .models.consumer.pubsub import PNPresenceEventResult, PNMessageResult, PNSignalMessageResult +from .models.consumer.pubsub import PNPresenceEventResult, PNMessageResult, PNSignalMessageResult, PNMessageActionResult from .models.server.subscribe import SubscribeMessage, PresenceEnvelope from .models.consumer.user import PNUserResult from .models.consumer.space import PNSpaceResult @@ -12,6 +12,11 @@ class SubscribeMessageWorker(object): + TYPE_MESSAGE = 0 + TYPE_SIGNAL = 1 + TYPE_OBJECT = 2 + TYPE_MESSAGE_ACTION = 3 + def __init__(self, pubnub_instance, listener_manager_instance, queue_instance, event): # assert isinstance(pubnub_instnace, PubNubCore) # assert isinstance(listener_manager_instance, ListenerManager) @@ -72,7 +77,7 @@ def _process_incoming_payload(self, message): timeout=message.payload.get('timeout', None) ) self._listener_manager.announce_presence(pn_presence_event_result) - elif message.is_object: + elif message.type == SubscribeMessageWorker.TYPE_OBJECT: if message.payload['type'] == 'user': user_result = PNUserResult( # pylint: disable=unexpected-keyword-arg,no-value-for-parameter event=message.payload['event'], @@ -97,7 +102,8 @@ def _process_incoming_payload(self, message): if extracted_message is None: logger.debug("unable to parse payload on #processIncomingMessages") - if message.is_signal: + + if message.type == SubscribeMessageWorker.TYPE_SIGNAL: pn_signal_result = PNSignalMessageResult( message=extracted_message, channel=channel, @@ -106,6 +112,13 @@ def _process_incoming_payload(self, message): publisher=publisher ) self._listener_manager.announce_signal(pn_signal_result) + elif message.type == SubscribeMessageWorker.TYPE_MESSAGE_ACTION: + message_action = extracted_message['data'] + if 'uuid' not in message_action: + message_action['uuid'] = publisher + + message_action_result = PNMessageActionResult(message_action) + self._listener_manager.announce_message_action(message_action_result) else: pn_message_result = PNMessageResult( message=extracted_message, @@ -114,5 +127,4 @@ def _process_incoming_payload(self, message): timetoken=publish_meta_data.publish_timetoken, publisher=publisher ) - self._listener_manager.announce_message(pn_message_result) From 33d4af66a46209931abae98d9b17568ce1fe491d Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 12:17:52 +0100 Subject: [PATCH 06/14] Resolve Travis errors in Python 2.7 and pypy. --- pubnub/endpoints/message_actions/__init__.py | 0 pubnub/pubnub_core.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 pubnub/endpoints/message_actions/__init__.py diff --git a/pubnub/endpoints/message_actions/__init__.py b/pubnub/endpoints/message_actions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index 6dd46c6d..b8bf70f6 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -3,9 +3,6 @@ from abc import ABCMeta, abstractmethod -from pubnub.endpoints.message_actions.add_message_action import AddMessageAction -from pubnub.endpoints.message_actions.get_message_actions import GetMessageActions -from pubnub.endpoints.message_actions.remove_message_action import RemoveMessageAction from .managers import BasePathManager, TokenManager, TokenManagerProperties from .builders import SubscribeBuilder from .builders import UnsubscribeBuilder @@ -43,6 +40,9 @@ from .endpoints.membership.get_members import GetMembers from .endpoints.membership.manage_members import ManageMembers from .endpoints.membership.manage_memberships import ManageMemberships +from .endpoints.message_actions.add_message_action import AddMessageAction +from .endpoints.message_actions.get_message_actions import GetMessageActions +from .endpoints.message_actions.remove_message_action import RemoveMessageAction from .endpoints.push.add_channels_to_push import AddChannelsToPush from .endpoints.push.remove_channels_from_push import RemoveChannelsFromPush From 434625555390b666d5d829cd9a04eafc0f75f787 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 12:20:58 +0100 Subject: [PATCH 07/14] Resolve Codacy errors. --- pubnub/endpoints/endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubnub/endpoints/endpoint.py b/pubnub/endpoints/endpoint.py index 92d870e7..5133a566 100644 --- a/pubnub/endpoints/endpoint.py +++ b/pubnub/endpoints/endpoint.py @@ -50,7 +50,7 @@ def validate_params(self): pass @abstractmethod - def create_response(self, endpoint): + def create_response(self, envelope): pass @abstractmethod From ae452f8122d77d866b9c094532afa16a74e09122 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 12:29:00 +0100 Subject: [PATCH 08/14] Suppress arguments-differ Codacy error. --- pubnub/endpoints/endpoint.py | 2 +- pubnub/endpoints/message_actions/add_message_action.py | 2 +- pubnub/endpoints/message_actions/remove_message_action.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubnub/endpoints/endpoint.py b/pubnub/endpoints/endpoint.py index 5133a566..92d870e7 100644 --- a/pubnub/endpoints/endpoint.py +++ b/pubnub/endpoints/endpoint.py @@ -50,7 +50,7 @@ def validate_params(self): pass @abstractmethod - def create_response(self, envelope): + def create_response(self, endpoint): pass @abstractmethod diff --git a/pubnub/endpoints/message_actions/add_message_action.py b/pubnub/endpoints/message_actions/add_message_action.py index dd38aaad..73d6899e 100644 --- a/pubnub/endpoints/message_actions/add_message_action.py +++ b/pubnub/endpoints/message_actions/add_message_action.py @@ -49,7 +49,7 @@ def validate_params(self): self.validate_channel() self.validate_message_action() - def create_response(self, envelope): + def create_response(self, envelope): # pylint: disable=W0221 return PNAddMessageActionResult(envelope['data']) def is_auth_required(self): diff --git a/pubnub/endpoints/message_actions/remove_message_action.py b/pubnub/endpoints/message_actions/remove_message_action.py index ef594ec7..fcf969f2 100644 --- a/pubnub/endpoints/message_actions/remove_message_action.py +++ b/pubnub/endpoints/message_actions/remove_message_action.py @@ -48,7 +48,7 @@ def validate_params(self): self.validate_channel() self.validate_timetokens() - def create_response(self, envelope): + def create_response(self, envelope): # pylint: disable=W0221 return {} def is_auth_required(self): From 79bb5e52456d8b7917437953b51cc455005e0cbd Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 15:07:20 +0100 Subject: [PATCH 09/14] Expose 'get_tokens' method --- pubnub/pubnub_core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index b8bf70f6..308c8728 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -263,6 +263,9 @@ def get_token_by_resource(self, resource_id, resource_type): resource_type=resource_type )) + def get_tokens(self): + return self._token_manager.get_tokens() + def get_tokens_by_resource(self, resource_type): return self._token_manager.get_tokens_by_resource(resource_type) From 7862ded5eedd6f7991683a89d73d11512c601360 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 16:15:12 +0100 Subject: [PATCH 10/14] Add include_meta to History --- pubnub/endpoints/history.py | 12 +++++++++++- pubnub/models/consumer/history.py | 15 ++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pubnub/endpoints/history.py b/pubnub/endpoints/history.py index ddb9c80c..26b2c32e 100644 --- a/pubnub/endpoints/history.py +++ b/pubnub/endpoints/history.py @@ -18,6 +18,7 @@ def __init__(self, pubnub): self._reverse = None self._count = None self._include_timetoken = None + self._include_meta = None def channel(self, channel): self._channel = channel @@ -48,6 +49,11 @@ def include_timetoken(self, include_timetoken): self._include_timetoken = include_timetoken return self + def include_meta(self, include_meta): + assert isinstance(include_meta, bool) + self._include_meta = include_meta + return self + def custom_params(self): params = {} @@ -68,6 +74,9 @@ def custom_params(self): if self._include_timetoken is not None: params['include_token'] = "true" if self._include_timetoken else "false" + if self._include_meta is not None: + params['include_meta'] = "true" if self._include_meta else "false" + return params def build_path(self): @@ -90,7 +99,8 @@ def create_response(self, envelope): return PNHistoryResult.from_json( json_input=envelope, crypto=self.pubnub.config.crypto, - include_tt_option=self._include_timetoken, + include_timetoken=self._include_timetoken, + include_meta=self._include_meta, cipher=self.pubnub.config.cipher_key) def request_timeout(self): diff --git a/pubnub/models/consumer/history.py b/pubnub/models/consumer/history.py index 148cdb32..140b6c6c 100644 --- a/pubnub/models/consumer/history.py +++ b/pubnub/models/consumer/history.py @@ -1,4 +1,3 @@ - class PNHistoryResult(object): def __init__(self, messages, start_timetoken, end_timetoken): self.messages = messages @@ -9,7 +8,7 @@ def __str__(self): return "History result for range %d..%d" % (self.start_timetoken, self.end_timetoken) @classmethod - def from_json(cls, json_input, crypto, include_tt_option=False, cipher=None): + def from_json(cls, json_input, crypto, include_timetoken=False, include_meta=False, cipher=None): start_timetoken = json_input[1] end_timetoken = json_input[2] @@ -17,8 +16,13 @@ def from_json(cls, json_input, crypto, include_tt_option=False, cipher=None): messages = [] for item in raw_items: - if isinstance(item, dict) and 'timetoken' in item and 'message' in item and include_tt_option: - message = PNHistoryItemResult(item['message'], crypto, item['timetoken']) + if (include_timetoken or include_meta) and isinstance(item, dict) and 'message' in item: + message = PNHistoryItemResult(item['message'], crypto) + if include_timetoken and 'timetoken' in item: + message.timetoken = item['timetoken'] + if include_meta and 'meta' in item: + message.meta = item['meta'] + else: message = PNHistoryItemResult(item, crypto) @@ -35,8 +39,9 @@ def from_json(cls, json_input, crypto, include_tt_option=False, cipher=None): class PNHistoryItemResult(object): - def __init__(self, entry, crypto, timetoken=None): + def __init__(self, entry, crypto, timetoken=None, meta=None): self.timetoken = timetoken + self.meta = meta self.entry = entry self.crypto = crypto From 2c3f4473651e0fb241b78ff9607e7edc523e598e Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 21:00:34 +0100 Subject: [PATCH 11/14] Add Fetch messages feature --- pubnub/endpoints/fetch_messages.py | 138 +++++++++++++++++++++++++++++ pubnub/enums.py | 1 + pubnub/errors.py | 4 +- pubnub/models/consumer/history.py | 48 ++++++++++ pubnub/pubnub_core.py | 4 + 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 pubnub/endpoints/fetch_messages.py diff --git a/pubnub/endpoints/fetch_messages.py b/pubnub/endpoints/fetch_messages.py new file mode 100644 index 00000000..ea02252e --- /dev/null +++ b/pubnub/endpoints/fetch_messages.py @@ -0,0 +1,138 @@ +import logging + +import six + +from pubnub import utils +from pubnub.endpoints.endpoint import Endpoint +from pubnub.enums import HttpMethod, PNOperationType +from pubnub.errors import PNERR_CHANNEL_MISSING, PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS +from pubnub.exceptions import PubNubException +from pubnub.models.consumer.history import PNFetchMessagesResult + +logger = logging.getLogger("pubnub") + + +class FetchMessages(Endpoint): + FETCH_MESSAGES_PATH = "/v3/history/sub-key/%s/channel/%s" + FETCH_MESSAGES_WITH_ACTIONS_PATH = "/v3/history-with-actions/sub-key/%s/channel/%s" + + DEFAULT_MESSAGES = 100 + MAX_MESSAGES = 25 + MAX_MESSAGES_ACTIONS = 100 + + def __init__(self, pubnub): + Endpoint.__init__(self, pubnub) + self._channels = [] + self._start = None + self._end = None + self._maximum_per_channel = None + self._include_meta = None + self._include_message_actions = None + + def channels(self, channels): + utils.extend_list(self._channels, channels) + return self + + def maximum_per_channel(self, maximum_per_channel): + assert isinstance(maximum_per_channel, six.integer_types) + self._maximum_per_channel = maximum_per_channel + return self + + def start(self, start): + assert isinstance(start, six.integer_types) + self._start = start + return self + + def end(self, end): + assert isinstance(end, six.integer_types) + self._end = end + return self + + def include_meta(self, include_meta): + assert isinstance(include_meta, bool) + self._include_meta = include_meta + return self + + def include_message_actions(self, include_message_actions): + assert isinstance(include_message_actions, bool) + self._include_message_actions = include_message_actions + return self + + def custom_params(self): + params = {'max': str(self._maximum_per_channel)} + + if self._start is not None: + params['start'] = str(self._start) + + if self._end is not None: + params['end'] = str(self._end) + + if self._include_meta is not None: + params['include_meta'] = "true" if self._include_meta else "false" + + return params + + def build_path(self): + if self._include_message_actions is False: + return FetchMessages.FETCH_MESSAGES_PATH % ( + self.pubnub.config.subscribe_key, + utils.join_channels(self._channels) + ) + else: + return FetchMessages.FETCH_MESSAGES_WITH_ACTIONS_PATH % ( + self.pubnub.config.subscribe_key, + utils.url_encode(self._channels[0]) + ) + + def http_method(self): + return HttpMethod.GET + + def is_auth_required(self): + return True + + def validate_params(self): + self.validate_subscribe_key() + + if self._channels is None or len(self._channels) == 0: + raise PubNubException(pn_error=PNERR_CHANNEL_MISSING) + + if self._include_meta is None: + self._include_meta = False + + if self._include_message_actions is None: + self._include_message_actions = False + + if self._include_message_actions is False: + if self._maximum_per_channel is None or self._maximum_per_channel < FetchMessages.DEFAULT_MESSAGES: + self._maximum_per_channel = FetchMessages.DEFAULT_MESSAGES + logger.info("maximum_per_channel param defaulting to %d", FetchMessages.DEFAULT_MESSAGES) + elif self._maximum_per_channel > FetchMessages.MAX_MESSAGES: + self._maximum_per_channel = FetchMessages.MAX_MESSAGES + logger.info("maximum_per_channel param defaulting to %d", FetchMessages.DEFAULT_MESSAGES) + else: + if len(self._channels) > 1: + raise PubNubException(pn_error=PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS) + + if self._maximum_per_channel is None or self._maximum_per_channel < 1 or\ + self._maximum_per_channel > FetchMessages.MAX_MESSAGES_ACTIONS: + self._maximum_per_channel = FetchMessages.MAX_MESSAGES_ACTIONS + logger.info("maximum_per_channel param defaulting to %d", FetchMessages.DEFAULT_MESSAGES) + + def create_response(self, envelope): + return PNFetchMessagesResult.from_json( + json_input=envelope, + include_message_actions=self._include_message_actions, + start_timetoken=self._start, + end_timetoken=self._end) + + def request_timeout(self): + return self.pubnub.config.non_subscribe_request_timeout + + def connect_timeout(self): + return self.pubnub.config.connect_timeout + + def operation_type(self): + return PNOperationType.PNFetchMessagesOperation + + def name(self): + return "Fetch messages" diff --git a/pubnub/enums.py b/pubnub/enums.py index 15fc27da..57ce831b 100644 --- a/pubnub/enums.py +++ b/pubnub/enums.py @@ -84,6 +84,7 @@ class PNOperationType(object): PNAddMessageAction = 42 PNGetMessageActions = 43 PNDeleteMessageAction = 44 + PNFetchMessagesOperation = 45 class PNHeartbeatNotificationOptions(object): diff --git a/pubnub/errors.py b/pubnub/errors.py index 6a6b246c..dc6b8fe8 100644 --- a/pubnub/errors.py +++ b/pubnub/errors.py @@ -39,5 +39,5 @@ PNERR_MESSAGE_TIMETOKEN_MISSING = "Message timetoken is missing" PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING = "Message action timetoken is missing" PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS = "History can return message action data for a single channel only. " \ - "Either pass a single channel or disable the includeMessageActions " \ - "flag. " + "Either pass a single channel or disable the include_message_action" \ + "s flag. " diff --git a/pubnub/models/consumer/history.py b/pubnub/models/consumer/history.py index 140b6c6c..abf3ff3a 100644 --- a/pubnub/models/consumer/history.py +++ b/pubnub/models/consumer/history.py @@ -50,3 +50,51 @@ def __str__(self): def decrypt(self, cipher_key): self.entry = self.crypto.decrypt(cipher_key, self.entry) + + +class PNFetchMessagesResult(object): + + def __init__(self, channels, start_timetoken, end_timetoken): + self.channels = channels + self.start_timetoken = start_timetoken + self.end_timetoken = end_timetoken + + def __str__(self): + return "Fetch messages result for range %d..%d" % (self.start_timetoken, self.end_timetoken) + + @classmethod + def from_json(cls, json_input, include_message_actions=False, start_timetoken=None, end_timetoken=None): + channels = {} + print(json_input['channels']) + + for key, entry in json_input['channels'].items(): + channels[key] = [] + for item in entry: + message = PNFetchMessageItem(item['message'], item['timetoken']) + if 'meta' in item: + message.meta = item['meta'] + + if include_message_actions: + if 'actions' in item: + message.actions = item['actions'] + else: + message.actions = {} + + channels[key].append(message) + + return PNFetchMessagesResult( + channels=channels, + start_timetoken=start_timetoken, + end_timetoken=end_timetoken + ) + + +class PNFetchMessageItem(object): + def __init__(self, message, timetoken, meta=None, actions=None): + self.message = message + self.meta = meta + self.timetoken = timetoken + self.actions = actions + + def __str__(self): + return "Fetch message item with tt: %s and content: %s" % (self.timetoken, self.message) diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index 308c8728..90734fe2 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -40,6 +40,7 @@ from .endpoints.membership.get_members import GetMembers from .endpoints.membership.manage_members import ManageMembers from .endpoints.membership.manage_memberships import ManageMemberships +from .endpoints.fetch_messages import FetchMessages from .endpoints.message_actions.add_message_action import AddMessageAction from .endpoints.message_actions.get_message_actions import GetMessageActions from .endpoints.message_actions.remove_message_action import RemoveMessageAction @@ -233,6 +234,9 @@ def manage_members(self): def manage_memberships(self): return ManageMemberships(self) + def fetch_messages(self): + return FetchMessages(self) + def add_message_action(self): return AddMessageAction(self) From b7b9408260ed16f303e7dfefbf23eae9ed759b2a Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 21:06:47 +0100 Subject: [PATCH 12/14] Bump version to 4.2.2. --- .pubnub.yml | 23 ++++++++++++++++++++++- CHANGELOG.md | 10 ++++++++++ pubnub/pubnub_core.py | 2 +- setup.py | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 6036d7b7..71d076a9 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,21 @@ name: python -version: 4.2.1 +version: 4.2.2 schema: 1 scm: github.com/pubnub/python changelog: + - version: v4.2.2 + date: Jan 28, 2020 + changes: + - type: feature + text: Implemented Message Actions API + - type: feature + text: Implemented Fetch Messages API + - type: feature + text: Added 'include_meta' to history() + - type: feature + text: Added 'include_meta' to fetch_messages() + - type: feature + text: Added 'include_message_actions' to fetch_messages() - version: v4.2.1 date: Jan 9, 2020 changes: @@ -191,6 +204,9 @@ features: - STORAGE-START-END - STORAGE-COUNT - STORAGE-MESSAGE-COUNT + - STORAGE-HISTORY-WITH-META + - STORAGE-FETCH-WITH-META + - STORAGE-FETCH-WITH-MESSAGE-ACTIONS time: - TIME-TIME subscribe: @@ -205,6 +221,7 @@ features: - SUBSCRIBE-USER-LISTENER - SUBSCRIBE-SPACE-LISTENER - SUBSCRIBE-MEMBERSHIP-LISTENER + - SUBSCRIBE-MESSAGE-ACTIONS-LISTENER signal: - SIGNAL-SEND objects: @@ -228,6 +245,10 @@ features: - OBJECTS-ADD-MEMBERS - OBJECTS-UPDATE-MEMBERS - OBJECTS-REMOVE-MEMBERS + message-actions: + - MESSAGE-ACTIONS-GET + - MESSAGE-ACTIONS-ADD + - MESSAGE-ACTIONS-REMOVE supported-platforms: - diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b98a457..37c2749b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [4.2.2](https://github.com/pubnub/python/tree/v4.2.2) + + [Full Changelog](https://github.com/pubnub/python/compare/v4.2.1...v4.2.2) + +- 🌟 Implemented Message Actions API +- 🌟 Implemented Fetch Messages API +- 🌟 Added 'include_meta' to history() +- 🌟 Added 'include_meta' to fetch_messages() +- 🌟 Added 'include_message_actions' to fetch_messages() + ## [4.2.1](https://github.com/pubnub/python/tree/v4.2.1) [Full Changelog](https://github.com/pubnub/python/compare/v4.2.0...v4.2.1) diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index 90734fe2..e4351a40 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -56,7 +56,7 @@ class PubNubCore: """A base class for PubNub Python API implementations""" - SDK_VERSION = "4.2.1" + SDK_VERSION = "4.2.2" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 diff --git a/setup.py b/setup.py index 58c35665..00edca7c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='pubnub', - version='4.2.1', + version='4.2.2', description='PubNub Real-time push service in the cloud', author='PubNub', author_email='support@pubnub.com', From 8d4703b74498d00a1fee6b492f21e52c0569a909 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 21:31:31 +0100 Subject: [PATCH 13/14] Disable lint check to resolve Codacy false positive. --- pubnub/endpoints/fetch_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubnub/endpoints/fetch_messages.py b/pubnub/endpoints/fetch_messages.py index ea02252e..11aaf09e 100644 --- a/pubnub/endpoints/fetch_messages.py +++ b/pubnub/endpoints/fetch_messages.py @@ -118,7 +118,7 @@ def validate_params(self): self._maximum_per_channel = FetchMessages.MAX_MESSAGES_ACTIONS logger.info("maximum_per_channel param defaulting to %d", FetchMessages.DEFAULT_MESSAGES) - def create_response(self, envelope): + def create_response(self, envelope): # pylint: disable=W0221 return PNFetchMessagesResult.from_json( json_input=envelope, include_message_actions=self._include_message_actions, From 3dbab8364546b5763b4a8d8c10a1850eb8dbc2e0 Mon Sep 17 00:00:00 2001 From: QSD_s Date: Tue, 28 Jan 2020 22:41:11 +0100 Subject: [PATCH 14/14] Bump version to 4.3.0. --- .pubnub.yml | 4 ++-- CHANGELOG.md | 4 ++-- pubnub/pubnub_core.py | 2 +- setup.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 71d076a9..ace3b9b8 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,9 @@ name: python -version: 4.2.2 +version: 4.3.0 schema: 1 scm: github.com/pubnub/python changelog: - - version: v4.2.2 + - version: v4.3.0 date: Jan 28, 2020 changes: - type: feature diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c2749b..69a39427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## [4.2.2](https://github.com/pubnub/python/tree/v4.2.2) +## [4.3.0](https://github.com/pubnub/python/tree/v4.3.0) - [Full Changelog](https://github.com/pubnub/python/compare/v4.2.1...v4.2.2) + [Full Changelog](https://github.com/pubnub/python/compare/v4.2.1...v4.3.0) - 🌟 Implemented Message Actions API - 🌟 Implemented Fetch Messages API diff --git a/pubnub/pubnub_core.py b/pubnub/pubnub_core.py index e4351a40..e5827662 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -56,7 +56,7 @@ class PubNubCore: """A base class for PubNub Python API implementations""" - SDK_VERSION = "4.2.2" + SDK_VERSION = "4.3.0" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 diff --git a/setup.py b/setup.py index 00edca7c..05487a0e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='pubnub', - version='4.2.2', + version='4.3.0', description='PubNub Real-time push service in the cloud', author='PubNub', author_email='support@pubnub.com',