Skip to content

Commit db2a79b

Browse files
committed
bug fixes
1 parent 8357063 commit db2a79b

File tree

12 files changed

+137
-82
lines changed

12 files changed

+137
-82
lines changed

src/mistapi/__api_request.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ def _request_with_retry(
253253
"apirequest:%s:Exception occurred", method_name, exc_info=True
254254
)
255255
break
256-
self._count += 1
256+
with self._token_lock:
257+
self._count += 1
257258
return APIResponse(url=url, response=resp, proxy_error=proxy_failed)
258259

259260
def mist_get(self, uri: str, query: dict[str, str] | None = None) -> APIResponse:

src/mistapi/__api_response.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
This module manages API responses
1212
"""
1313

14+
import re
15+
1416
from requests import Response
1517
from requests.structures import CaseInsensitiveDict
1618

@@ -85,7 +87,11 @@ def _check_next(self) -> None:
8587
separator = "&" if "?" in uri else "?"
8688
self.next = f"{uri}{separator}page={page + 1}"
8789
else:
88-
self.next = uri.replace(f"page={page}", f"page={page + 1}")
90+
self.next = re.sub(
91+
rf"(?<=[?&])page={page}(?=&|$)",
92+
f"page={page + 1}",
93+
uri,
94+
)
8995
logger.debug(f"apiresponse:_check_next:set next to {self.next}")
9096
except ValueError:
9197
logger.error(

src/mistapi/__api_session.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def _get_api_token_data(self, apitoken) -> tuple[str | None, list | None]:
580580
if data_json.get("email"):
581581
token_type = "user" # nosec bandit B105
582582

583-
for priv in data_json.get("privileges"):
583+
for priv in data_json.get("privileges", []):
584584
tmp = {
585585
"scope": priv.get("scope"),
586586
"role": priv.get("role"),
@@ -715,7 +715,7 @@ def _process_login(self, retry: bool = True) -> str | None:
715715
"email/password cleaned up. Restarting authentication function"
716716
)
717717
if retry:
718-
return self._process_login(retry)
718+
return self._process_login(retry=False)
719719
except requests.exceptions.ProxyError as proxy_error:
720720
LOGGER.critical("apisession:_process_login:proxy not valid...")
721721
CONSOLE.critical("Proxy not valid...\r\n")
@@ -935,9 +935,15 @@ def _set_authenticated(self, authentication_status: bool) -> None:
935935
LOGGER.error(
936936
"apirequest:mist_post_file: Exception occurred", exc_info=True
937937
)
938-
self._csrftoken = self._session.cookies["csrftoken" + cookies_ext]
939-
self._session.headers.update({"X-CSRFToken": self._csrftoken})
940-
LOGGER.info("apisession:_set_authenticated:CSRF Token stored")
938+
csrf_cookie = self._session.cookies.get("csrftoken" + cookies_ext)
939+
if csrf_cookie:
940+
self._csrftoken = csrf_cookie
941+
self._session.headers.update({"X-CSRFToken": self._csrftoken})
942+
LOGGER.info("apisession:_set_authenticated:CSRF Token stored")
943+
else:
944+
LOGGER.error(
945+
"apisession:_set_authenticated:CSRF Token cookie not found"
946+
)
941947
elif authentication_status is False:
942948
self._authenticated = False
943949
LOGGER.info(
@@ -1093,7 +1099,7 @@ def _getself(self) -> bool:
10931099
for key, val in resp.data.items():
10941100
if key == "privileges":
10951101
self.privileges = Privileges(resp.data["privileges"])
1096-
if key == "tags":
1102+
elif key == "tags":
10971103
for tag in resp.data["tags"]:
10981104
self.tags.append(tag)
10991105
else:

src/mistapi/device_utils/__tools/bgp.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def summary(
4040
UUID of the site where the device is located.
4141
device_id : str
4242
UUID of the device to show BGP summary on.
43+
timeout : int, default 5
44+
Time in seconds to wait for data before closing the connection.
4345
on_message : Callable, optional
4446
Callback invoked with each extracted raw message as it arrives.
4547

src/mistapi/device_utils/ex.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@
4747
)
4848
from mistapi.device_utils.__tools.shell import interactive_shell as interactiveShell
4949

50-
# Tools (ping, monitor traffic)
50+
# Tools (ping, traceroute, monitor traffic)
51+
from mistapi.device_utils.__tools.miscellaneous import TracerouteProtocol
5152
from mistapi.device_utils.__tools.miscellaneous import monitor_traffic as monitorTraffic
5253
from mistapi.device_utils.__tools.miscellaneous import ping
5354
from mistapi.device_utils.__tools.miscellaneous import top_command as topCommand
55+
from mistapi.device_utils.__tools.miscellaneous import traceroute
5456

5557
# Policy functions
5658
from mistapi.device_utils.__tools.policy import clear_hit_count as clearHitCount
@@ -88,4 +90,6 @@
8890
"monitorTraffic",
8991
"ping",
9092
"topCommand",
93+
"traceroute",
94+
"TracerouteProtocol",
9195
]

src/mistapi/device_utils/srx.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@
3737
)
3838
from mistapi.device_utils.__tools.shell import interactive_shell as interactiveShell
3939

40-
# Tools (ping, monitor traffic)
40+
# Tools (ping, traceroute, monitor traffic)
41+
from mistapi.device_utils.__tools.miscellaneous import TracerouteProtocol
4142
from mistapi.device_utils.__tools.miscellaneous import monitor_traffic as monitorTraffic
4243
from mistapi.device_utils.__tools.miscellaneous import ping
4344
from mistapi.device_utils.__tools.miscellaneous import top_command as topCommand
45+
from mistapi.device_utils.__tools.miscellaneous import traceroute
4446

4547
# OSPF functions
4648
from mistapi.device_utils.__tools.ospf import show_database as retrieveOspfDatabase
@@ -52,6 +54,7 @@
5254
from mistapi.device_utils.__tools.port import bounce as bouncePort
5355

5456
# Route functions
57+
from mistapi.device_utils.__tools.routes import RouteProtocol
5558
from mistapi.device_utils.__tools.routes import show as retrieveRoutes
5659

5760
# Sessions functions
@@ -61,6 +64,8 @@
6164
__all__ = [
6265
# Classes/Enums
6366
"Node",
67+
"RouteProtocol",
68+
"TracerouteProtocol",
6469
# ARP
6570
"retrieveArpTable",
6671
# BGP
@@ -88,4 +93,5 @@
8893
"monitorTraffic",
8994
"ping",
9095
"topCommand",
96+
"traceroute",
9197
]

src/mistapi/device_utils/ssr.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
from mistapi.device_utils.__tools.dhcp import release_dhcp_leases as releaseDhcpLeases
3131
from mistapi.device_utils.__tools.dhcp import retrieve_dhcp_leases as retrieveDhcpLeases
3232

33-
# Tools (ping only - no monitor_traffic for SSR)
33+
# Tools (ping, traceroute - no monitor_traffic for SSR)
34+
from mistapi.device_utils.__tools.miscellaneous import TracerouteProtocol
3435
from mistapi.device_utils.__tools.miscellaneous import ping
36+
from mistapi.device_utils.__tools.miscellaneous import traceroute
3537

3638
# DNS functions
3739
# from mistapi.utils.dns import test_resolution as test_dns_resolution
@@ -45,6 +47,7 @@
4547
from mistapi.device_utils.__tools.port import bounce as bouncePort
4648

4749
# Route functions
50+
from mistapi.device_utils.__tools.routes import RouteProtocol
4851
from mistapi.device_utils.__tools.routes import show as retrieveRoutes
4952

5053
# Service Path functions
@@ -59,6 +62,8 @@
5962
__all__ = [
6063
# Classes/Enums
6164
"Node",
65+
"RouteProtocol",
66+
"TracerouteProtocol",
6267
# ARP
6368
"retrieveArpTable",
6469
# BGP
@@ -84,4 +89,5 @@
8489
"clearSessions",
8590
# Tools
8691
"ping",
92+
"traceroute",
8793
]

src/mistapi/websockets/__ws_client.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(
7474
self._on_message_cb: Callable[[dict], None] | None = None
7575
self._on_error_cb: Callable[[Exception], None] | None = None
7676
self._on_open_cb: Callable[[], None] | None = None
77-
self._on_close_cb: Callable[[int, str], None] | None = None
77+
self._on_close_cb: Callable[[int | None, str | None], None] | None = None
7878

7979
# ------------------------------------------------------------------
8080
# Auth / URL helpers
@@ -141,7 +141,7 @@ def on_open(self, callback: Callable[[], None]) -> None:
141141
"""Register a callback invoked when the connection is established."""
142142
self._on_open_cb = callback
143143

144-
def on_close(self, callback: Callable[[int, str], None]) -> None:
144+
def on_close(self, callback: Callable[[int | None, str | None], None]) -> None:
145145
"""Register a callback invoked when the connection closes."""
146146
self._on_close_cb = callback
147147

@@ -176,8 +176,8 @@ def _handle_error(self, ws: websocket.WebSocketApp, error: Exception) -> None:
176176
def _handle_close(
177177
self,
178178
ws: websocket.WebSocketApp,
179-
close_status_code: int,
180-
close_msg: str,
179+
close_status_code: int | None,
180+
close_msg: str | None,
181181
) -> None:
182182
self._connected.clear()
183183
self._last_close_code = close_status_code
@@ -208,7 +208,7 @@ def connect(self, run_in_background: bool = True) -> None:
208208
If True, runs the WebSocket loop in a daemon thread (non-blocking).
209209
If False, blocks the calling thread until disconnected.
210210
"""
211-
if self._thread is not None and self._thread.is_alive():
211+
if self._connected.is_set() or (self._thread is not None and self._thread.is_alive()):
212212
raise RuntimeError("Already connected; call disconnect() first")
213213
self._user_disconnect.clear()
214214
self._reconnect_attempts = 0
@@ -270,10 +270,7 @@ def _run_forever_safe(self) -> None:
270270
# Final close: put sentinel and call callback
271271
self._queue.put(None)
272272
if self._on_close_cb:
273-
# websocket-client may provide None for close code/message; normalize
274-
code = self._last_close_code if self._last_close_code is not None else -1
275-
msg = self._last_close_msg if self._last_close_msg is not None else ""
276-
self._on_close_cb(code, msg)
273+
self._on_close_cb(self._last_close_code, self._last_close_msg)
277274

278275
def disconnect(self) -> None:
279276
"""Close the WebSocket connection."""
@@ -291,7 +288,12 @@ def receive(self) -> Generator[dict, None, None]:
291288
292289
Intended for use after connect(run_in_background=True).
293290
"""
294-
if not self._connected.wait(timeout=10):
291+
if self._auto_reconnect:
292+
while not self._connected.is_set() and not self._user_disconnect.is_set():
293+
self._connected.wait(timeout=1)
294+
if self._user_disconnect.is_set() and not self._connected.is_set():
295+
return
296+
elif not self._connected.wait(timeout=10):
295297
return
296298
while True:
297299
try:

0 commit comments

Comments
 (0)