Exemple #1
0
 def _send_subscribe_request(self):
     logger.debug('_send_subscribe_request(%s)', self._args)
     args = {}
     if self._delivery_mode.value & SubscriptionMode.FAST_FORWARD.value:
         args[u'fast_forward'] = True
     if self._args:
         args.update(self._args)
     self._send_subscribe_request_(args)
Exemple #2
0
 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[u'position'])
         if self.observer:
             self.observer.on_subscription_data(data)
Exemple #3
0
 def unsubscribe(self):
     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())
Exemple #4
0
 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()
Exemple #5
0
    def update_position(self, new_position):
        logger.debug('update_position %s', new_position)
        if not (self._delivery_mode.value
                & SubscriptionMode.TRACK_POSITION.value):
            return

        if self._args:
            self._args[u'position'] = new_position
        else:
            self._args = {u'position': new_position}
 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)
Exemple #7
0
    def subscribe(self, args=None, observer=None):
        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())
Exemple #8
0
 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)
Exemple #9
0
    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
Exemple #10
0
 def on_subscribe_ok(self, ack):
     logger.debug('on_subscribe_ok')
     if ack.get(u'body').get(u'position'):
         self.update_position(ack[u'body'][u'position'])
     self._sm.advance(lambda sm: sm.SubscribeOK())
Exemple #11
0
 def connect(self):
     logger.debug('connect')
     self._connected = True
     self._sm.advance(lambda sm: sm.Connect())
Exemple #12
0
 def on_unsubscribe_error(self):
     logger.debug('on_unsubscribe_error')
     self._sm.advance(lambda sm: sm.UnsubscribeError())
Exemple #13
0
 def on_unsubscribe_ok(self):
     logger.debug('on_unsubscribe_ok')
     self._sm.advance(lambda sm: sm.UnsubscribeOK())
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.

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(self, 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()
=================== ======================

.. note:: Regardless of the protocol you choose when you create your client, the
          ``data`` parameter contains Python objects.

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()
Exemple #15
0
    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')
            # TODO: distinguish errors and legitimate resubscriptions
            #       and call an error callback on former
            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)

                body = ack.get('body')
                reason = 'Unknown error'
                if body:
                    reason = body.get('reason') or body.get('error')

                subscription.on_subscribe_error(reason)
            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 and s.mode != 'cycle':
                    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
Exemple #16
0
 def _send_unsubscribe_request(self):
     logger.debug('_send_unsubscribe_request')
     self._send_unsubscribe_request_()
Exemple #17
0
 def disconnect(self):
     logger.debug('disconnect')
     self._connected = False
     self._sm.advance(lambda sm: sm.Disconnect())
Exemple #18
0
    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:
                try:
                    subscription.on_subscription_data(data)
                except Exception as e:
                    logger.error("Exception in on_subscription_data")
                    logger.exception(e)
                    self._queue.put(a.Stop())
            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_preserialized_message(
                        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_preserialized_value(m.key, m.value,
                                                      m.callback)
        elif t == a.Delete:
            self.connection.delete(m.key, 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.SolicitedPDU:
            try:
                m.callback(m.payload)
            except Exception as e:
                logger.error("Exception in a solicited PDU callback")
                logger.exception(e)
                self._queue.put(a.Stop())
        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
Exemple #19
0
 def on_subscribe_error(self, reason):
     logger.debug('on_subscribe_error')
     self._sm.advance(lambda sm: sm.SubscribeError(reason))