def _send_subscribe_request(self): logger.debug('_send_subscribe_request(%s)', self._args) args = {} if self._delivery_mode.value & SubscriptionMode.FAST_FORWARD.value: args['fast_forward'] = True if self._args: args.update(self._args) self._send_subscribe_request_(args)
def callback(outcome): logger.debug('Outcome: %s', outcome) if type(outcome) == auth.Done: logger.debug('Restored auth') counter[0] -= 1 if counter[0] == 0: ready_event.set() else: ready_event.set()
def on_subscription_data(self, data): logger.debug('Got channel data %s', data) accepting_states =\ ['Subscription.' + s for s in ['Subscribed', 'Unsubscribing']] if self._sm.get_state_name() in accepting_states: self.update_position(data['position']) if self.observer: self.observer.on_subscription_data(data)
def _subscribe(self, channel, mode, subscription_observer=None, args=None): logger.info('_subscribe') old_subscription = self.subscriptions.get(channel) if old_subscription: logger.debug('Old subscription found') old_subscription.subscribe(args, observer=subscription_observer) return def subscribe_callback(ack): logger.info('SAck: %s', ack) if ack.get('action') == 'rtm/subscribe/ok': subscription.on_subscribe_ok(ack) elif ack.get('action') == 'rtm/subscribe/error': logger.error('Subscription error: %s', ack) subscription.on_subscribe_error() else: self.on_internal_error( 'Unexpected subscribe ack: {0}'.format(ack)) def send_subscribe_request(args_): try: self.connection.subscribe(channel, args_, callback=subscribe_callback) except Exception as e: logger.exception(e) def unsubscribe_callback(ack): logger.info('USAck for channel %s: %s', channel, ack) if ack.get('action') == 'rtm/unsubscribe/ok': s = self.subscriptions.get(channel) if s: del self.subscriptions[channel] subscription.on_unsubscribe_ok() if s and not subscription.deleted(): self.subscriptions[channel] = s elif ack.get('action') == 'rtm/unsubscribe/error': subscription.on_unsubscribe_error() else: self.on_internal_error( 'Unexpected unsubscribe ack: {0}'.format(ack)) def send_unsubscribe_request(): try: self.connection.unsubscribe(channel, unsubscribe_callback) except Exception as e: logger.exception(e) subscription = Subscription(mode, send_subscribe_request, send_unsubscribe_request, args) subscription.observer = subscription_observer if self.is_connected(): subscription.connect() self.subscriptions[channel] = subscription
def unsubscribe(self): with self._lock: self._args = None if self.is_failed(): self._mode = 'unlinked' self._sm.advance(lambda sm: sm.UnsubscribeOK()) else: logger.debug('unsubscribe') self._mode = 'unlinked' self._sm.advance(lambda sm: sm.ModeChange())
def go(g): if self._sm_transition_queue: logger.debug('Appending to transition queue') self._sm_transition_queue.append(g) else: self._sm_transition_queue.append(g) logger.debug( '%s performing transition', self._sm.__class__.__name__) g(self._sm) self._sm_transition_queue.popleft() while self._sm_transition_queue: h = self._sm_transition_queue.popleft() go(h)
def subscribe(self, args=None, observer=None): with self._lock: logger.debug('subscribe') if self._mode in ['linked', 'cycle']: logger.error('Already subscribed or trying to') return if self._next_observer: try: self._next_observer.on_deleted() except Exception: pass self._next_observer = observer self._next_args = args self._mode = 'cycle' return self._sm.advance(lambda sm: sm.ModeChange())
def _perform_state_callback(self, callback_name, *args): if self.observer: current_thread_name = threading.current_thread().name logger.debug('entering subscription callback %s on %s', callback_name, current_thread_name) callback = None try: callback = getattr(self.observer, callback_name) except AttributeError: pass try: if callback: callback(*args) except Exception as e: logger.error('Caught exception in subscription callback') logger.exception(e) finally: logger.debug('exiting subscription callback %s on %s', callback_name, current_thread_name)
def _restore_auth_and_return_true_if_failed(self): logger.info('_restore_auth_and_return_true_if_failed') if not self.restore_auth_on_reconnect: return False counter = [len(self._successful_auth_delegates)] logger.debug('Restoring %d authentications', counter[0]) if counter[0] == 0: return False ready_event = threading.Event() def callback(outcome): logger.debug('Outcome: %s', outcome) if type(outcome) == auth.Done: logger.debug('Restored auth') counter[0] -= 1 if counter[0] == 0: ready_event.set() else: ready_event.set() for ad in self._successful_auth_delegates: ready_event.clear() try: self.connection.authenticate(ad, callback) except Exception as e: logger.exception(e) ready_event.wait(10) if counter[0] == 0: logger.debug('Restoring authentications: done') return False else: logger.error('Failed to restore %d authentications', counter[0]) return True
def make_client(*args, **kwargs): r""" make_client(\*args, \*\*kwargs) ------------------------------- Description The `make_client()` function is a context manager. Call `make_client()` using a `with` statement and the SDK automatically starts the WebSocket connection. The SDK stops and then closes the WebSocket connection when the statement completes or terminates due to an error. This function takes the same parameters as the Client constructor plus optional `auth_delegate`. To use this function, import it from the client module:: `from satori.rtm.client import make_client` Parameters * endpoint {string} [required] - RTM endpoint as a string. Example: "wss://rtm:8443/foo/bar". If port number is omitted, it defaults to 80 for ws:// and 443 for wss://. Available from the Dev Portal. * appkey {string} [required] - Appkey used to access RTM. Available from the Dev Portal. * reconnect_interval {int} [optional] - Time period, in seconds, between reconnection attempts. The timeout period between each successive connection attempt increases, but starts with this value. Use max_reconnect_interval to specify the maximum number of seconds between reconnection attempts. Default is 1. * max_reconnect_interval {int} [optional] - Maximum period of time, in seconds, to wait between reconnection attempts. Default is 300. * fail_count_threshold {int} [optional] - Number of times the SDK should attempt to reconnect if the connection disconnects. Specify any value that resolves to an integer. Default is inf (infinity). * observer {client_observer} [optional] - Instance of a client observer class, used to define functionality based on the state changes of a Client. Set this property with client.observer or in the `make_client(*args, **kwargs)` or `Client(*args, **kwargs)` methods. * restore_auth_on_reconnect {boolean} optional - Whether to restore authentication after reconnects. Default is True. * max_queue_size {int} optional - this parameter limits the amount of concurrent requests in order to avoid 'out of memory' situation. For example is max_queue_size is 10 and the client code sends 11 publish requests so fast that by the time it sends 11th one the reply for the first one has not yet arrived, this 11th call to `client.publish` will throw the `satori.rtm.client.Full` exception. * auth_delegate {AuthDelegate} [optional] - if auth_delegate parameter is present, the client yielded by make_client will be already authenticated. Syntax :: import satori.rtm.client as sc endpoint = 'ENDPOINT' appkey = 'APPKEY' with sc.make_client(endpoint=endpoint, appkey=appkey) as client: Client Observer --------------- Use the client observer callback functions in an observer to implement functionality based on the Client object state changes. Set this observer with the `client.observer` property on the Client. The following table lists the Client object states and the associated callback functions: ============ ====================== ===================== Client State Enter Callback Exit Callback ============ ====================== ===================== Awaiting on_enter_awaiting() on_leave_awaiting() Connecting on_enter_connecting() on_leave_connecting() Connected on_enter_connected() on_leave_connected() Stopped on_enter_stopped() on_leave_stopped() Disposed on_enter_disposed() n/a ============ ====================== ===================== The following figure shows an example client observer with implemented callback function:: class ClientObserver(object): def __init__(self): self.connection_attempt_count = 0 def on_enter_connecting(self): self.connection_attempt_count += 1 print('Establishing connection #{0}'.format( self.connection_attempt_count)) client = Client(endpoint='<ENDPOINT>', appkey=None) client.observer = ClientObserver() client.start() client.stop() client.start() Subscription Observer --------------------- Use callback functions in a subscription observer to implement functionality based on the state changes for a channel subscription. The subscribe(channel, SubscriptionMode.RELIABLE, subscription_observer, args) method takes a subscription observer for the subscription_observer parameter. .. note:: Depending on your application, these callbacks are optional, except `on_subscription_data`. To process received messages, you must implement `on_subscription_data(data)` callback. The following table lists a subscription observer subscription states and callback functions: ============= ======================== ======================== State Enter Callback Exit Callback ============= ======================== ======================== Subscribing on_enter_subscribing() on_leave_subscribing() Subscribed on_enter_subscribed() on_leave_subscribed() Unsubscribing on_enter_unsubscribing() on_leave_unsubscribing() Unsubscribed on_enter_unsubscribed() on_leave_unsubscribed() Failed on_enter_failed() on_leave_failed() Deleted on_deleted() n/a ============= ======================== ======================== Other Callbacks =================== ====================== Event Callback =================== ====================== Created on_created() Message(s) Received on_subscription_data() =================== ====================== The following figure shows an example subscription observer with an implemented callback function:: class SubscriptionObserver(object): def __init__(self, channel): self.message_count = 0 self.channel = channel def on_subscription_data(self, data): for message in data['messages']: print('Got message {0}'.format(message)) self.message_count += len(data['messages']) def on_enter_subscribed(self): print('Subscription is now active') def on_deleted(self): print('Received {0} messages from channel ""{1}""'.format( self.message_count, self.channel)) subscription_observer = SubscriptionObserver() client.subscribe( channel, SubscriptionMode.RELIABLE, subscription_observer(channel)) # wait for some time client.unsubscribe(channel) """ observer = kwargs.get('observer') auth_delegate = kwargs.get('auth_delegate') if 'auth_delegate' in kwargs: del kwargs['auth_delegate'] client = Client(*args, **kwargs) ready_event = threading.Event() class Observer(ClientStateObserver): def on_enter_connected(self): ClientStateObserver.on_enter_connected(self) ready_event.set() def on_enter_stopped(self): ClientStateObserver.on_enter_stopped(self) ready_event.set() client.observer = Observer() client.start() if not ready_event.wait(70): if client.last_connecting_error(): client.dispose() raise RuntimeError( "Client connection timeout, last connection error: {0}".format( client.last_connecting_error())) else: raise RuntimeError("Client connection timeout") ready_event.clear() if not client.is_connected(): client.dispose() raise RuntimeError("Client connection error: {0}".format( client.last_connecting_error())) auth_mailbox = [] def auth_callback(auth_result): auth_mailbox.append(auth_result) ready_event.set() if auth_delegate: client.authenticate(auth_delegate, callback=auth_callback) if not ready_event.wait(20): client.dispose() raise AuthError('Authentication process has timed out') auth_result = auth_mailbox[0] if type(auth_result) == auth.Error: raise AuthError(auth_result.message) logger.debug('Auth success in make_client') try: client.observer = observer yield client finally: logger.info('make_client.finally') client.dispose()
def connect(self): logger.debug('connect') with self._lock: self._connected = True self._sm.advance(lambda sm: sm.Connect())
def on_unsubscribe_ok(self): with self._lock: logger.debug('on_unsubscribe_ok') self._sm.advance(lambda sm: sm.UnsubscribeOK())
def on_subscribe_ok(self, ack): with self._lock: logger.debug('on_subscribe_ok') if ack.get('body').get('position'): self.update_position(ack['body']['position']) self._sm.advance(lambda sm: sm.SubscribeOK())
def _send_unsubscribe_request(self): logger.debug('_send_unsubscribe_request') self._send_unsubscribe_request_()
def disconnect(self): logger.debug('disconnect') with self._lock: self._connected = False self._sm.advance(lambda sm: sm.Disconnect())
def process_one_message(self, timeout=1): '''Must be called from a single thread returns True if the message was Dispose()''' try: m = self._queue.get(block=True, timeout=timeout) except queue.Empty: logger.debug('queue is empty') return False t = type(m) logger.info('Begin handling %s', t.__name__) if t == a.ChannelData: data = m.data channel = data['subscription_id'] subscription = self.subscriptions.get(channel) if subscription: subscription.on_subscription_data(data) else: logger.error('Subscription for %s not found', data) elif t == a.Start: self._sm.Start() elif t == a.Stop: self._sm.Stop() elif t == a.Dispose: self._sm.Dispose() self._queue.task_done() return True elif t == a.Publish: if self.is_connected(): try: self.connection.publish(m.channel, m.message, m.callback) except Exception as e: logger.exception(e) else: self._offline_queue.append(m) elif t == a.Subscribe: self._subscribe(m.channel_or_subscription_id, m.mode, m.observer, args=m.args) elif t == a.Unsubscribe: self._unsubscribe(m.channel_or_subscription_id) elif t == a.Read: self.connection.read(m.key, m.args, m.callback) elif t == a.Write: self.connection.write(m.key, m.value, m.callback) elif t == a.Delete: self.connection.delete(m.key, m.callback) elif t == a.Search: self.connection.search(m.prefix, m.callback) elif t == a.Authenticate: if self.is_connected(): self._authenticate(m.auth_delegate, m.callback) else: self._offline_queue.append(m) elif t == a.Tick: self._sm.Tick() elif t == a.ConnectingComplete: self._sm.ConnectingComplete() elif t == a.ConnectingFailed: self._sm.ConnectingFailed() elif t == a.ConnectionClosed: self._sm.ConnectionClosed() elif t == a.ChannelError: self._sm.ChannelError(m.channel, m.payload) elif t == a.InternalError: self._sm.InternalError(m.payload) elif t == a.FastForward: self._perform_state_callback('on_fast_forward', m.channel) else: logger.error('Unexpected event %s: %s', m, t) self._queue.task_done() logger.info('Finish handling %s', t.__name__) return False
def on_unsubscribe_error(self): with self._lock: logger.debug('on_unsubscribe_error') self._sm.advance(lambda sm: sm.UnsubscribeError())