Skip to content

Commit 6a6c7a5

Browse files
committed
bug fixes and other improvements
1 parent 92ff338 commit 6a6c7a5

6 files changed

Lines changed: 100 additions & 51 deletions

File tree

src/mistapi/device_utils/__tools/shell.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def _build_sslopt(self) -> dict:
127127
session = self._mist_session._session
128128
if session.verify is False:
129129
sslopt["cert_reqs"] = ssl.CERT_NONE
130+
sslopt["check_hostname"] = False
130131
elif isinstance(session.verify, str):
131132
sslopt["ca_certs"] = session.verify
132133
if session.cert:
@@ -143,15 +144,21 @@ def _build_sslopt(self) -> dict:
143144

144145
def connect(self) -> None:
145146
"""Open the WebSocket connection."""
147+
if self._ws is not None and self._ws.connected:
148+
raise RuntimeError("Already connected; call disconnect() first")
146149
LOGGER.info("Connecting to shell WebSocket: %s", self._ws_url)
147150
self._ws = websocket.create_connection(
148151
self._ws_url,
149152
header=self._get_headers(),
150153
cookie=self._get_cookie(),
151154
sslopt=self._build_sslopt(),
152155
)
153-
self._ws.settimeout(0.1)
154-
self.resize(self._rows, self._cols)
156+
try:
157+
self._ws.settimeout(0.1)
158+
self.resize(self._rows, self._cols)
159+
except Exception:
160+
self.disconnect()
161+
raise
155162
LOGGER.info("Shell WebSocket connected")
156163

157164
def disconnect(self) -> None:
@@ -210,8 +217,16 @@ def recv(self, timeout: float = 0.1) -> bytes | None:
210217
finally:
211218
try:
212219
ws.settimeout(old_timeout)
213-
except Exception:
214-
pass
220+
except (
221+
websocket.WebSocketConnectionClosedException,
222+
ConnectionError,
223+
OSError,
224+
) as exc:
225+
LOGGER.debug(
226+
"ShellSession.recv: failed to restore websocket timeout "
227+
"(socket may be closed): %s",
228+
exc,
229+
)
215230

216231
def resize(self, rows: int, cols: int) -> None:
217232
"""Send a terminal resize message to the device."""

src/mistapi/websockets/__ws_client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ def __init__(
4949
max_reconnect_attempts: int = 5,
5050
reconnect_backoff: float = 2.0,
5151
) -> None:
52+
if max_reconnect_attempts < 0:
53+
raise ValueError("max_reconnect_attempts must be >= 0")
54+
if reconnect_backoff <= 0:
55+
raise ValueError("reconnect_backoff must be > 0")
56+
5257
self._mist_session = mist_session
5358
self._channels = channels
5459
self._ping_interval = ping_interval
@@ -75,7 +80,7 @@ def __init__(
7580
# Auth / URL helpers
7681

7782
def _build_ws_url(self) -> str:
78-
return f"wss://{self._mist_session._cloud_uri.replace('api.', 'api-ws.')}/api-ws/v1/stream"
83+
return f"wss://{self._mist_session._cloud_uri.replace('api.', 'api-ws.', 1)}/api-ws/v1/stream"
7984

8085
def _get_headers(self) -> dict:
8186
if self._mist_session._apitoken:
@@ -109,6 +114,7 @@ def _build_sslopt(self) -> dict:
109114
session = self._mist_session._session
110115
if session.verify is False:
111116
sslopt["cert_reqs"] = ssl.CERT_NONE
117+
sslopt["check_hostname"] = False
112118
elif isinstance(session.verify, str):
113119
sslopt["ca_certs"] = session.verify
114120
if session.cert:
@@ -146,6 +152,8 @@ def _handle_open(self, ws: websocket.WebSocketApp) -> None:
146152
for channel in self._channels:
147153
ws.send(json.dumps({"subscribe": channel}))
148154
self._reconnect_attempts = 0
155+
self._last_close_code = None
156+
self._last_close_msg = None
149157
self._connected.set()
150158
if self._on_open_cb:
151159
self._on_open_cb()
@@ -200,6 +208,8 @@ def connect(self, run_in_background: bool = True) -> None:
200208
If True, runs the WebSocket loop in a daemon thread (non-blocking).
201209
If False, blocks the calling thread until disconnected.
202210
"""
211+
if self._thread is not None and self._thread.is_alive():
212+
raise RuntimeError("Already connected; call disconnect() first")
203213
self._user_disconnect.clear()
204214
self._reconnect_attempts = 0
205215
# Drain stale sentinel from previous connection

src/mistapi/websockets/location.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class BleAssetsEvents(_MistWebsocket):
3434
ping_timeout : int, default 10
3535
Time in seconds to wait for a ping response before considering the connection dead.
3636
auto_reconnect : bool, default False
37-
Automatically reconnect on transient failures using exponential backoff.
37+
Automatically reconnect on unexpected disconnections using exponential backoff.
3838
max_reconnect_attempts : int, default 5
3939
Maximum number of reconnect attempts before giving up.
4040
reconnect_backoff : float, default 2.0
@@ -45,22 +45,22 @@ class BleAssetsEvents(_MistWebsocket):
4545
-----------
4646
Callback style (background thread)::
4747
48-
ws = LocationBleAssetsEvents(session, site_id="abc123", map_id="def456")
48+
ws = BleAssetsEvents(session, site_id="abc123", map_id="def456")
4949
ws.on_message(lambda data: print(data))
5050
ws.connect() # non-blocking, runs in background thread
5151
input("Press Enter to stop")
5252
ws.disconnect()
5353
5454
Generator style (background thread)::
5555
56-
ws = LocationBleAssetsEvents(session, site_id="abc123", map_id="def456")
56+
ws = BleAssetsEvents(session, site_id="abc123", map_id="def456")
5757
ws.connect(run_in_background=True)
5858
for msg in ws.receive():
5959
process(msg)
6060
6161
Context manager::
6262
63-
with LocationBleAssetsEvents(session, site_id="abc123", map_id="def456") as ws:
63+
with BleAssetsEvents(session, site_id="abc123", map_id="def456") as ws:
6464
ws.on_message(my_handler)
6565
ws.connect() # non-blocking, runs in background thread
6666
time.sleep(60)
@@ -107,27 +107,33 @@ class ConnectedClientsEvents(_MistWebsocket):
107107
Interval in seconds to send WebSocket ping frames (keep-alive).
108108
ping_timeout : int, default 10
109109
Time in seconds to wait for a ping response before considering the connection dead.
110+
auto_reconnect : bool, default False
111+
Automatically reconnect on unexpected disconnections using exponential backoff.
112+
max_reconnect_attempts : int, default 5
113+
Maximum number of reconnect attempts before giving up.
114+
reconnect_backoff : float, default 2.0
115+
Base backoff delay in seconds. Doubles after each failed attempt.
110116
111117
EXAMPLE
112118
-----------
113119
Callback style (background thread)::
114120
115-
ws = LocationConnectedClientsEvents(session, site_id="abc123", map_id="def456")
121+
ws = ConnectedClientsEvents(session, site_id="abc123", map_id="def456")
116122
ws.on_message(lambda data: print(data))
117123
ws.connect() # non-blocking, runs in background thread
118124
input("Press Enter to stop")
119125
ws.disconnect()
120126
121127
Generator style (background thread)::
122128
123-
ws = LocationConnectedClientsEvents(session, site_id="abc123", map_id="def456")
129+
ws = ConnectedClientsEvents(session, site_id="abc123", map_id="def456")
124130
ws.connect(run_in_background=True)
125131
for msg in ws.receive():
126132
process(msg)
127133
128134
Context manager::
129135
130-
with LocationConnectedClientsEvents(session, site_id="abc123", map_id="def456") as ws:
136+
with ConnectedClientsEvents(session, site_id="abc123", map_id="def456") as ws:
131137
ws.on_message(my_handler)
132138
ws.connect() # non-blocking, runs in background thread
133139
time.sleep(60)
@@ -174,27 +180,33 @@ class SdkClientsEvents(_MistWebsocket):
174180
Interval in seconds to send WebSocket ping frames (keep-alive).
175181
ping_timeout : int, default 10
176182
Time in seconds to wait for a ping response before considering the connection dead.
183+
auto_reconnect : bool, default False
184+
Automatically reconnect on unexpected disconnections using exponential backoff.
185+
max_reconnect_attempts : int, default 5
186+
Maximum number of reconnect attempts before giving up.
187+
reconnect_backoff : float, default 2.0
188+
Base backoff delay in seconds. Doubles after each failed attempt.
177189
178190
EXAMPLE
179191
-----------
180192
Callback style (background thread)::
181193
182-
ws = LocationSdkClientsEvents(session, site_id="abc123", map_id="def456")
194+
ws = SdkClientsEvents(session, site_id="abc123", map_id="def456")
183195
ws.on_message(lambda data: print(data))
184196
ws.connect() # non-blocking, runs in background thread
185197
input("Press Enter to stop")
186198
ws.disconnect()
187199
188200
Generator style (background thread)::
189201
190-
ws = LocationSdkClientsEvents(session, site_id="abc123", map_id="def456")
202+
ws = SdkClientsEvents(session, site_id="abc123", map_id="def456")
191203
ws.connect(run_in_background=True)
192204
for msg in ws.receive():
193205
process(msg)
194206
195207
Context manager::
196208
197-
with LocationSdkClientsEvents(session, site_id="abc123", map_id="def456") as ws:
209+
with SdkClientsEvents(session, site_id="abc123", map_id="def456") as ws:
198210
ws.on_message(my_handler)
199211
ws.connect() # non-blocking, runs in background thread
200212
time.sleep(60)
@@ -241,27 +253,33 @@ class UnconnectedClientsEvents(_MistWebsocket):
241253
Interval in seconds to send WebSocket ping frames (keep-alive).
242254
ping_timeout : int, default 10
243255
Time in seconds to wait for a ping response before considering the connection dead.
256+
auto_reconnect : bool, default False
257+
Automatically reconnect on unexpected disconnections using exponential backoff.
258+
max_reconnect_attempts : int, default 5
259+
Maximum number of reconnect attempts before giving up.
260+
reconnect_backoff : float, default 2.0
261+
Base backoff delay in seconds. Doubles after each failed attempt.
244262
245263
EXAMPLE
246264
-----------
247265
Callback style (background thread)::
248266
249-
ws = LocationUnconnectedClientsEvents(session, site_id="abc123", map_id="def456")
267+
ws = UnconnectedClientsEvents(session, site_id="abc123", map_id="def456")
250268
ws.on_message(lambda data: print(data))
251269
ws.connect() # non-blocking, runs in background thread
252270
input("Press Enter to stop")
253271
ws.disconnect()
254272
255273
Generator style (background thread)::
256274
257-
ws = LocationUnconnectedClientsEvents(session, site_id="abc123", map_id="def456")
275+
ws = UnconnectedClientsEvents(session, site_id="abc123", map_id="def456")
258276
ws.connect(run_in_background=True)
259277
for msg in ws.receive():
260278
process(msg)
261279
262280
Context manager::
263281
264-
with LocationUnconnectedClientsEvents(session, site_id="abc123", map_id="def456") as ws:
282+
with UnconnectedClientsEvents(session, site_id="abc123", map_id="def456") as ws:
265283
ws.on_message(my_handler)
266284
ws.connect() # non-blocking, runs in background thread
267285
time.sleep(60)
@@ -310,27 +328,33 @@ class DiscoveredBleAssetsEvents(_MistWebsocket):
310328
Interval in seconds to send WebSocket ping frames (keep-alive).
311329
ping_timeout : int, default 10
312330
Time in seconds to wait for a ping response before considering the connection dead.
331+
auto_reconnect : bool, default False
332+
Automatically reconnect on unexpected disconnections using exponential backoff.
333+
max_reconnect_attempts : int, default 5
334+
Maximum number of reconnect attempts before giving up.
335+
reconnect_backoff : float, default 2.0
336+
Base backoff delay in seconds. Doubles after each failed attempt.
313337
314338
EXAMPLE
315339
-----------
316340
Callback style (background thread)::
317341
318-
ws = LocationDiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456")
342+
ws = DiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456")
319343
ws.on_message(lambda data: print(data))
320344
ws.connect() # non-blocking, runs in background thread
321345
input("Press Enter to stop")
322346
ws.disconnect()
323347
324348
Generator style (background thread)::
325349
326-
ws = LocationDiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456")
350+
ws = DiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456")
327351
ws.connect(run_in_background=True)
328352
for msg in ws.receive():
329353
process(msg)
330354
331355
Context manager::
332356
333-
with LocationDiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456") as ws:
357+
with DiscoveredBleAssetsEvents(session, site_id="abc123", map_id="def456") as ws:
334358
ws.on_message(my_handler)
335359
ws.connect() # non-blocking, runs in background thread
336360
time.sleep(60)

src/mistapi/websockets/orgs.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class InsightsEvents(_MistWebsocket):
3232
ping_timeout : int, default 10
3333
Time in seconds to wait for a ping response before considering the connection dead.
3434
auto_reconnect : bool, default False
35-
Automatically reconnect on transient failures using exponential backoff.
35+
Automatically reconnect on unexpected disconnections using exponential backoff.
3636
max_reconnect_attempts : int, default 5
3737
Maximum number of reconnect attempts before giving up.
3838
reconnect_backoff : float, default 2.0
@@ -42,22 +42,22 @@ class InsightsEvents(_MistWebsocket):
4242
-----------
4343
Callback style (background thread)::
4444
45-
ws = OrgInsightsEvents(session, org_id="abc123")
45+
ws = InsightsEvents(session, org_id="abc123")
4646
ws.on_message(lambda data: print(data))
4747
ws.connect() # non-blocking, runs in background thread
4848
input("Press Enter to stop")
4949
ws.disconnect()
5050
5151
Generator style::
5252
53-
ws = OrgInsightsEvents(session, org_id="abc123")
53+
ws = InsightsEvents(session, org_id="abc123")
5454
ws.connect(run_in_background=True)
5555
for msg in ws.receive():
5656
process(msg)
5757
5858
Context manager::
5959
60-
with OrgInsightsEvents(session, org_id="abc123") as ws:
60+
with InsightsEvents(session, org_id="abc123") as ws:
6161
ws.on_message(my_handler)
6262
ws.connect() # non-blocking, runs in background thread
6363
time.sleep(60)
@@ -101,7 +101,7 @@ class MxEdgesStatsEvents(_MistWebsocket):
101101
ping_timeout : int, default 10
102102
Time in seconds to wait for a ping response before considering the connection dead.
103103
auto_reconnect : bool, default False
104-
Automatically reconnect on transient failures using exponential backoff.
104+
Automatically reconnect on unexpected disconnections using exponential backoff.
105105
max_reconnect_attempts : int, default 5
106106
Maximum number of reconnect attempts before giving up.
107107
reconnect_backoff : float, default 2.0
@@ -111,22 +111,22 @@ class MxEdgesStatsEvents(_MistWebsocket):
111111
-----------
112112
Callback style (background thread)::
113113
114-
ws = OrgMxEdgesStatsEvents(session, org_id="abc123")
114+
ws = MxEdgesStatsEvents(session, org_id="abc123")
115115
ws.on_message(lambda data: print(data))
116116
ws.connect() # non-blocking, runs in background thread
117117
input("Press Enter to stop")
118118
ws.disconnect()
119119
120120
Generator style::
121121
122-
ws = OrgMxEdgesStatsEvents(session, org_id="abc123")
122+
ws = MxEdgesStatsEvents(session, org_id="abc123")
123123
ws.connect(run_in_background=True)
124124
for msg in ws.receive():
125125
process(msg)
126126
127127
Context manager::
128128
129-
with OrgMxEdgesStatsEvents(session, org_id="abc123") as ws:
129+
with MxEdgesStatsEvents(session, org_id="abc123") as ws:
130130
ws.on_message(my_handler)
131131
ws.connect() # non-blocking, runs in background thread
132132
time.sleep(60)
@@ -170,7 +170,7 @@ class MxEdgesEvents(_MistWebsocket):
170170
ping_timeout : int, default 10
171171
Time in seconds to wait for a ping response before considering the connection dead.
172172
auto_reconnect : bool, default False
173-
Automatically reconnect on transient failures using exponential backoff.
173+
Automatically reconnect on unexpected disconnections using exponential backoff.
174174
max_reconnect_attempts : int, default 5
175175
Maximum number of reconnect attempts before giving up.
176176
reconnect_backoff : float, default 2.0

src/mistapi/websockets/session.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SessionWithUrl(_MistWebsocket):
3333
ping_timeout : int, default 10
3434
Time in seconds to wait for a ping response before considering the connection dead.
3535
auto_reconnect : bool, default False
36-
Automatically reconnect on transient failures using exponential backoff.
36+
Automatically reconnect on unexpected disconnections using exponential backoff.
3737
max_reconnect_attempts : int, default 5
3838
Maximum number of reconnect attempts before giving up.
3939
reconnect_backoff : float, default 2.0
@@ -43,22 +43,22 @@ class SessionWithUrl(_MistWebsocket):
4343
-----------
4444
Callback style (background thread)::
4545
46-
ws = sessionWithUrl(session, url="wss://example.com/channel")
46+
ws = SessionWithUrl(session, url="wss://example.com/channel")
4747
ws.on_message(lambda data: print(data))
4848
ws.connect() # non-blocking, runs in background thread
4949
input("Press Enter to stop")
5050
ws.disconnect()
5151
5252
Generator style::
5353
54-
ws = sessionWithUrl(session, url="wss://example.com/channel")
54+
ws = SessionWithUrl(session, url="wss://example.com/channel")
5555
ws.connect(run_in_background=True)
5656
for msg in ws.receive():
5757
process(msg)
5858
5959
Context manager::
6060
61-
with sessionWithUrl(session, url="wss://example.com/channel") as ws:
61+
with SessionWithUrl(session, url="wss://example.com/channel") as ws:
6262
ws.on_message(my_handler)
6363
ws.connect() # non-blocking, runs in background thread
6464
time.sleep(60)

0 commit comments

Comments
 (0)