+class Monitor:
+ """
+ Monitor helper class for our Connection class.
+
+ Contains a reference to an instantiated Connection, along with a
+ reference to the Connection.receiver Future. Upon inspecttion of
+ these objects, this class determines whether the connection is in
+ an 'error', 'connected' or 'disconnected' state.
+
+ Use this class to stay up to date on the health of a connection,
+ and take appropriate action if the connection errors out due to
+ network issues or other unexpected circumstances.
+
+ """
+ ERROR = 'error'
+ CONNECTED = 'connected'
+ DISCONNECTING = 'disconnecting'
+ DISCONNECTED = 'disconnected'
+
+ def __init__(self, connection):
+ self.connection = weakref.ref(connection)
+ self.reconnecting = asyncio.Lock(loop=connection.loop)
+ self.close_called = asyncio.Event(loop=connection.loop)
+ self.receiver_stopped = asyncio.Event(loop=connection.loop)
+ self.pinger_stopped = asyncio.Event(loop=connection.loop)
+ self.receiver_stopped.set()
+ self.pinger_stopped.set()
+
+ @property
+ def status(self):
+ """
+ Determine the status of the connection and receiver, and return
+ ERROR, CONNECTED, or DISCONNECTED as appropriate.
+
+ For simplicity, we only consider ourselves to be connected
+ after the Connection class has setup a receiver task. This
+ only happens after the websocket is open, and the connection
+ isn't usable until that receiver has been started.
+
+ """
+ connection = self.connection()
+
+ # the connection instance was destroyed but someone kept
+ # a separate reference to the monitor for some reason
+ if not connection:
+ return self.DISCONNECTED
+
+ # connection cleanly disconnected or not yet opened
+ if not connection.ws:
+ return self.DISCONNECTED
+
+ # close called but not yet complete
+ if self.close_called.is_set():
+ return self.DISCONNECTING
+
+ # connection closed uncleanly (we didn't call connection.close)
+ if self.receiver_stopped.is_set() or not connection.ws.open:
+ return self.ERROR
+
+ # everything is fine!
+ return self.CONNECTED
+
+