Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "mist_openapi"]
path = mist_openapi
url = https://github.com/mistsys/mist_openapi.git
branch = 2602.1.7
branch = 2602.1.8
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
# CHANGELOG
## Version 0.61.3 (March 2026)

**Released**: March 18, 2026

This release hardens the WebSocket client with improved thread-safety, bounded message queues, and better error handling.

---

### 1. NEW FEATURES

#### **Bounded Message Queue (`queue_maxsize`)**
The `_MistWebsocket` client now supports a `queue_maxsize` parameter to limit the internal message buffer size. When set, incoming messages are dropped with a warning when the queue is full, preventing unbounded memory growth on high-frequency streams.

```python
ws = mistapi.websockets.sites.DeviceStatsEvents(
apisession,
site_ids=["<site_id>"],
queue_maxsize=1000 # Limit buffer to 1000 messages
)
```

---

### 2. IMPROVEMENTS

#### **Thread-Safety Enhancements**
- Added `threading.Lock()` to protect shared state during concurrent access
- Added `_finished` event for cleaner lifecycle management and proper shutdown signaling

#### **Error Handling Hardening**
- User-provided callbacks (`on_open`, `on_message`, `on_error`, `on_close`) are now wrapped in try/except blocks to prevent exceptions from crashing the WebSocket thread
- Added warning when `cloud_uri` does not start with `"api."` (WebSocket URL may be incorrect)

#### **Security: Header Redaction**
- Added `_HeaderRedactFilter` to automatically redact `Authorization` and `Cookie` headers from `websocket-client` debug logs

---

### 3. API CHANGES

#### **Insights API**
Added optional `port_id` query parameter to the following functions for port-level filtering:
- **`getSiteInsightMetricsForDevice()`**
- **`getSiteInsightMetricsForGateway()`**
- **`getSiteInsightMetricsForMxEdge()`**
- **`getSiteInsightMetricsForSwitch()`**

---

## Version 0.61.2 (March 2026)

**Released**: March 17, 2026
Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ A comprehensive Python package to interact with the Mist Cloud APIs, built from
- [Examples](#examples)
- [WebSocket Streaming](#websocket-streaming)
- [Connection Parameters](#connection-parameters)
- [Callbacks](#callbacks)
- [Methods](#methods)
- [Available Channels](#available-channels)
- [Usage Patterns](#usage-patterns)
- [Async Usage](#async-usage)
Expand Down Expand Up @@ -588,6 +588,7 @@ All channel classes accept the following optional keyword arguments:
| `auto_reconnect` | `bool` | `False` | Automatically reconnect on transient failures using exponential backoff. |
| `max_reconnect_attempts` | `int` | `5` | Maximum number of reconnect attempts before giving up. |
| `reconnect_backoff` | `float` | `2.0` | Base backoff delay in seconds. Doubles after each failed attempt (2s, 4s, 8s, ...). Resets on successful reconnection. |
| `queue_maxsize` | `int` | `0` | Maximum messages buffered in the internal queue for `receive()`. `0` means unbounded. When set, incoming messages are dropped with a warning when the queue is full, preventing memory growth on high-frequency streams. |

```python
ws = mistapi.websockets.sites.DeviceStatsEvents(
Expand All @@ -600,15 +601,18 @@ ws = mistapi.websockets.sites.DeviceStatsEvents(
ws.connect()
```

### Callbacks
### Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `ws.on_open(cb)` | `cb()` | Called when the connection is established |
| `ws.on_message(cb)` | `cb(data: dict)` | Called for every incoming message |
| `ws.on_error(cb)` | `cb(error: Exception)` | Called on WebSocket errors |
| `ws.on_close(cb)` | `cb(status_code: int, msg: str)` | Called when the connection closes |
| `ws.ready()` | `-> bool \| None` | Returns `True` if the connection is open and ready |
| `ws.on_open(cb)` | `cb()` | Register callback for connection established |
| `ws.on_message(cb)` | `cb(data: dict)` | Register callback for incoming messages. Mutually exclusive with `receive()`. |
| `ws.on_error(cb)` | `cb(error: Exception)` | Register callback for WebSocket errors |
| `ws.on_close(cb)` | `cb(code: int \| None, msg: str \| None)` | Register callback for connection close. Safe to call `connect()` from within. |
| `ws.connect(run_in_background)` | | Open the connection. `True` (default) runs in a daemon thread; `False` blocks. |
| `ws.disconnect(wait, timeout)` | | Close the connection. `wait=True` blocks until the background thread finishes. |
| `ws.receive()` | `-> Generator[dict]` | Blocking generator yielding messages. Mutually exclusive with `on_message`. |
| `ws.ready()` | `-> bool` | Returns `True` if the connection is open and ready |

### Available Channels

Expand Down
2 changes: 1 addition & 1 deletion mist_openapi
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "mistapi"
version = "0.61.2"
version = "0.61.3"
authors = [{ name = "Thomas Munzer", email = "tmunzer@juniper.net" }]
description = "Python package to simplify the Mist System APIs usage"
keywords = ["Mist", "Juniper", "API"]
Expand Down Expand Up @@ -64,7 +64,7 @@ dev = [
"twine>=6.1.0",
"build>=1.2.2.post1",
"pyyaml>=6.0.2",
"urllib3>=2.4.0",
"urllib3>=2.6.3",
]

# Hatchling configuration (replaces setup.cfg)
Expand Down
35 changes: 19 additions & 16 deletions scripts/generate_from_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,23 +669,26 @@ def _create_get(
if code:
has_deprecated = True

# Generate main function parameters and documentation
code_path_params, desc_path_params = _gen_code_params(path_params, operation_id)
code_query_params, desc_query_params = _gen_code_params(query_params, operation_id)
code_query = _gen_query_code(query_params)
code_desc = _gen_description(
operation_id, tags, desc_path_params, desc_query_params
)
else:
# Generate main function parameters and documentation
code_path_params, desc_path_params = _gen_code_params(path_params, operation_id)
code_query_params, desc_query_params = _gen_code_params(
query_params, operation_id
)
code_query = _gen_query_code(query_params)
code_desc = _gen_description(
operation_id, tags, desc_path_params, desc_query_params
)

# Generate main GET function code
code += FUNCTION_GET_TEMPLATE.format(
operation_id=operation_id,
code_path_params=code_path_params,
code_query_params=code_query_params,
code_desc=code_desc,
uri=_gen_uri(endpoint_path),
query_code=code_query,
)
# Generate main GET function code
code += FUNCTION_GET_TEMPLATE.format(
operation_id=operation_id,
code_path_params=code_path_params,
code_query_params=code_query_params,
code_desc=code_desc,
uri=_gen_uri(endpoint_path),
query_code=code_query,
)
return code, has_deprecated


Expand Down
2 changes: 1 addition & 1 deletion src/mistapi/__version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "0.61.2"
__version__ = "0.61.3"
__author__ = "Thomas Munzer <tmunzer@juniper.net>"
16 changes: 16 additions & 0 deletions src/mistapi/api/v1/sites/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def getSiteInsightMetricsForDevice(
site_id: str,
metric: str,
device_mac: str,
port_id: str | None = None,
start: str | None = None,
end: str | None = None,
duration: str | None = None,
Expand All @@ -223,6 +224,7 @@ def getSiteInsightMetricsForDevice(

QUERY PARAMS
------------
port_id : str
start : str
end : str
duration : str, default: 1d
Expand All @@ -238,6 +240,8 @@ def getSiteInsightMetricsForDevice(

uri = f"/api/v1/sites/{site_id}/insights/device/{device_mac}/{metric}"
query_params: dict[str, str] = {}
if port_id:
query_params["port_id"] = str(port_id)
if start:
query_params["start"] = str(start)
if end:
Expand Down Expand Up @@ -398,6 +402,7 @@ def getSiteInsightMetricsForGateway(
site_id: str,
device_id: str,
metrics: str,
port_id: str | None = None,
start: str | None = None,
end: str | None = None,
duration: str | None = None,
Expand All @@ -421,6 +426,7 @@ def getSiteInsightMetricsForGateway(
QUERY PARAMS
------------
metrics : str
port_id : str
start : str
end : str
duration : str, default: 1d
Expand All @@ -438,6 +444,8 @@ def getSiteInsightMetricsForGateway(
query_params: dict[str, str] = {}
if metrics:
query_params["metrics"] = str(metrics)
if port_id:
query_params["port_id"] = str(port_id)
if start:
query_params["start"] = str(start)
if end:
Expand All @@ -459,6 +467,7 @@ def getSiteInsightMetricsForMxEdge(
site_id: str,
metric: str,
device_mac: str,
port_id: str | None = None,
start: str | None = None,
end: str | None = None,
duration: str | None = None,
Expand All @@ -482,6 +491,7 @@ def getSiteInsightMetricsForMxEdge(

QUERY PARAMS
------------
port_id : str
start : str
end : str
duration : str, default: 1d
Expand All @@ -497,6 +507,8 @@ def getSiteInsightMetricsForMxEdge(

uri = f"/api/v1/sites/{site_id}/insights/mxedge/{device_mac}/{metric}"
query_params: dict[str, str] = {}
if port_id:
query_params["port_id"] = str(port_id)
if start:
query_params["start"] = str(start)
if end:
Expand Down Expand Up @@ -624,6 +636,7 @@ def getSiteInsightMetricsForSwitch(
site_id: str,
metric: str,
device_mac: str,
port_id: str | None = None,
start: str | None = None,
end: str | None = None,
duration: str | None = None,
Expand All @@ -647,6 +660,7 @@ def getSiteInsightMetricsForSwitch(

QUERY PARAMS
------------
port_id : str
start : str
end : str
duration : str, default: 1d
Expand All @@ -662,6 +676,8 @@ def getSiteInsightMetricsForSwitch(

uri = f"/api/v1/sites/{site_id}/insights/switch/{device_mac}/{metric}"
query_params: dict[str, str] = {}
if port_id:
query_params["port_id"] = str(port_id)
if start:
query_params["start"] = str(start)
if end:
Expand Down
7 changes: 3 additions & 4 deletions src/mistapi/api/v1/sites/sle.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@
--------------------------------------------------------------------------------
"""

import deprecation

from mistapi import APISession as _APISession
from mistapi.__api_response import APIResponse as _APIResponse
import deprecation


@deprecation.deprecated(
deprecated_in="0.59.2",
removed_in="0.65.0",
current_version="0.61.2",
current_version="0.61.3",
details="function replaced with getSiteSleClassifierSummaryTrend",
)
def getSiteSleClassifierDetails(
Expand Down Expand Up @@ -691,7 +690,7 @@ def listSiteSleImpactedWirelessClients(
@deprecation.deprecated(
deprecated_in="0.59.2",
removed_in="0.65.0",
current_version="0.61.2",
current_version="0.61.3",
details="function replaced with getSiteSleSummaryTrend",
)
def getSiteSleSummary(
Expand Down
Loading
Loading