diff --git a/.pubnub.yml b/.pubnub.yml index 6036d7b7..ace3b9b8 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,21 @@ name: python -version: 4.2.1 +version: 4.3.0 schema: 1 scm: github.com/pubnub/python changelog: + - version: v4.3.0 + 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..69a39427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [4.3.0](https://github.com/pubnub/python/tree/v4.3.0) + + [Full Changelog](https://github.com/pubnub/python/compare/v4.2.1...v4.3.0) + +- 🌟 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/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/endpoints/fetch_messages.py b/pubnub/endpoints/fetch_messages.py new file mode 100644 index 00000000..11aaf09e --- /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): # pylint: disable=W0221 + 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/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/endpoints/message_actions/__init__.py b/pubnub/endpoints/message_actions/__init__.py new file mode 100644 index 00000000..e69de29b 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..73d6899e --- /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): # pylint: disable=W0221 + 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..fcf969f2 --- /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): # pylint: disable=W0221 + 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/enums.py b/pubnub/enums.py index 6d7ef510..57ce831b 100644 --- a/pubnub/enums.py +++ b/pubnub/enums.py @@ -81,6 +81,10 @@ class PNOperationType(object): PNManageMembershipsOperation = 40 PNAccessManagerGrantToken = 41 + PNAddMessageAction = 42 + PNGetMessageActions = 43 + PNDeleteMessageAction = 44 + PNFetchMessagesOperation = 45 class PNHeartbeatNotificationOptions(object): diff --git a/pubnub/errors.py b/pubnub/errors.py index 2f3eaa6d..dc6b8fe8 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 include_message_action" \ + "s flag. " diff --git a/pubnub/managers.py b/pubnub/managers.py index 1c51cc26..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) @@ -484,6 +488,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 @@ -579,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/consumer/history.py b/pubnub/models/consumer/history.py index 148cdb32..abf3ff3a 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 @@ -45,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/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): """ 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/pubnub_core.py b/pubnub/pubnub_core.py index 5da55ff2..e5827662 100644 --- a/pubnub/pubnub_core.py +++ b/pubnub/pubnub_core.py @@ -40,6 +40,10 @@ 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 from .endpoints.push.add_channels_to_push import AddChannelsToPush from .endpoints.push.remove_channels_from_push import RemoveChannelsFromPush @@ -52,7 +56,7 @@ class PubNubCore: """A base class for PubNub Python API implementations""" - SDK_VERSION = "4.2.1" + SDK_VERSION = "4.3.0" SDK_NAME = "PubNub-Python" TIMESTAMP_DIVIDER = 1000 @@ -230,6 +234,18 @@ 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) + + def get_message_actions(self): + return GetMessageActions(self) + + def remove_message_action(self): + return RemoveMessageAction(self) + def time(self): return Time(self) @@ -251,6 +267,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) 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) diff --git a/setup.py b/setup.py index 58c35665..05487a0e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='pubnub', - version='4.2.1', + version='4.3.0', description='PubNub Real-time push service in the cloud', author='PubNub', author_email='support@pubnub.com',