コード例 #1
0
 def _start_disconnecting(self):
     logger.info('_start_disconnecting')
     if self.connection:
         try:
             self.connection.stop()
         except Exception as e:
             logger.exception(e)
コード例 #2
0
 def _unsubscribe(self, channel):
     logger.info('unsubscribe from %s', channel)
     old_subscription = self.subscriptions.get(channel)
     if old_subscription:
         logger.info('found old subscription')
         old_subscription.unsubscribe()
     else:
         logger.error("Trying to unsubscribe from unknown channel %s",
                      channel)
コード例 #3
0
 def _forget_connection(self):
     logger.info('_forget_connection')
     if self.connection:
         self.connection.ack_callbacks_by_id.clear()
         self.connection.delegate = None
         try:
             self.connection.stop()
         except Exception:
             pass
         self.connection = None
     self._disconnect_subscriptions()
コード例 #4
0
 def _connect(self):
     logger.info('_connect')
     self._time_of_last_reconnect = time.time()
     self.connection = Connection(self._endpoint, self._appkey, self,
                                  self.https_proxy, self._protocol)
     try:
         self.connection.start()
         self._queue.put(a.ConnectingComplete())
     except Exception as e:
         logger.exception(e)
         self.last_connecting_error = e
         self._queue.put(a.ConnectingFailed())
コード例 #5
0
 def _perform_state_callback(self, callback_name, *args):
     try:
         logger.info('entering callback %s', callback_name)
         try:
             callback = getattr(self.observer, callback_name)
             callback(*args)
         except AttributeError:
             pass
     except Exception as e:
         logger.error('Caught exception in state callback')
         logger.exception(e)
         self._queue.put(a.Stop())
     finally:
         logger.info('exiting callback %s', callback_name)
コード例 #6
0
        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))
コード例 #7
0
        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))
コード例 #8
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
コード例 #9
0
 def on_leave_connecting(self):
     logger.info('on_leave_connecting')
コード例 #10
0
 def _fail_count_is_small(self):
     result = self._connection_attempts_left > 0
     logger.info('_fail_count_is_small = %s', result)
     return result
コード例 #11
0
 def on_enter_awaiting(self):
     logger.info('on_enter_awaiting')
コード例 #12
0
 def _increment_fail_count(self):
     logger.info('_increment_fail_count: %s fails left',
                 self._connection_attempts_left)
     self._fail_count += 1
     self._connection_attempts_left -= 1
コード例 #13
0
 def _set_fail_count_to_critical(self):
     logger.info('_set_fail_count_to_critical')
     self._fail_count = self.fail_count_threshold
     self._connection_attempts_left = 0
コード例 #14
0
 def on_enter_disposed(self):
     logger.info('on_enter_disposed')
コード例 #15
0
 def on_leave_stopping(self):
     logger.info('on_leave_stopping')
コード例 #16
0
 def on_enter_connecting(self):
     logger.info('on_enter_connecting')
コード例 #17
0
 def on_leave_connected(self):
     logger.info('on_leave_connected')
コード例 #18
0
 def _drain_offline_queue(self):
     logger.info('_drain_offline_queue')
     while self._offline_queue:
         action = self._offline_queue.popleft()
         self._queue.put(action)
コード例 #19
0
 def on_leave_awaiting(self):
     logger.info('on_leave_awaiting')
コード例 #20
0
 def on_connection_closed(self):
     logger.info('on_connection_closed')
     self._queue.put(a.ConnectionClosed())
コード例 #21
0
 def on_fast_forward(self, channel, payload):
     logger.info('on_fast_forward')
     self._queue.put(a.FastForward(channel, payload))
コード例 #22
0
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()
コード例 #23
0
 def _disconnect_subscriptions(self):
     logger.info('_disconnect_subscriptions')
     existing = list(self.subscriptions.items())
     for _, subscription in existing:
         subscription.disconnect()
コード例 #24
0
 def _connect_subscriptions(self):
     logger.info('connect_subscriptions')
     for _, s in six.iteritems(self.subscriptions):
         s.connect()
コード例 #25
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
コード例 #26
0
 def _reset_fail_count(self):
     logger.info('_reset_fail_count')
     self._fail_count = 0
     self._connection_attempts_left = self.fail_count_threshold
コード例 #27
0
 def on_enter_connected(self):
     logger.info('on_enter_connected')
コード例 #28
0
 def on_enter_stopping(self):
     logger.info('on_enter_stopping')
コード例 #29
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
コード例 #30
0
 def on_leave_stopped(self):
     logger.info('on_leave_stopped')