botversion-sdk 2.1.0__tar.gz → 2.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/PKG-INFO +1 -1
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/__init__.py +1 -1
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/bin/scan_endpoints.py +1 -1
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/client.py +51 -12
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/interceptor.py +670 -138
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/scanner.py +400 -3
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/PKG-INFO +1 -1
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/setup.py +1 -1
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk/bin/__init__.py +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/SOURCES.txt +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/dependency_links.txt +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/entry_points.txt +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/requires.txt +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/botversion_sdk.egg-info/top_level.txt +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/pyproject.toml +0 -0
- {botversion_sdk-2.1.0 → botversion_sdk-2.2.1}/setup.cfg +0 -0
|
@@ -158,7 +158,7 @@ def init(app=None, api_key=None, **options):
|
|
|
158
158
|
|
|
159
159
|
_client = BotVersionClient({
|
|
160
160
|
"api_key": api_key,
|
|
161
|
-
"platform_url": options.get("platform_url", "
|
|
161
|
+
"platform_url": options.get("platform_url", "http://localhost:3000"),
|
|
162
162
|
"debug": debug,
|
|
163
163
|
"timeout": options.get("timeout", 30),
|
|
164
164
|
"flush_delay": options.get("flush_delay", 3),
|
|
@@ -28,7 +28,7 @@ def main():
|
|
|
28
28
|
_load_env_files(cwd)
|
|
29
29
|
|
|
30
30
|
api_key = os.environ.get("BOTVERSION_API_KEY")
|
|
31
|
-
platform_url = os.environ.get("BOTVERSION_PLATFORM_URL", "
|
|
31
|
+
platform_url = os.environ.get("BOTVERSION_PLATFORM_URL", "http://localhost:3000")
|
|
32
32
|
|
|
33
33
|
if not api_key:
|
|
34
34
|
print("[botversion] BOTVERSION_API_KEY environment variable is not set. Skipping backend endpoint scan.")
|
|
@@ -6,12 +6,13 @@ import urllib.parse
|
|
|
6
6
|
import urllib.error
|
|
7
7
|
import atexit
|
|
8
8
|
import time
|
|
9
|
+
from collections import deque
|
|
9
10
|
|
|
10
11
|
class BotVersionClient:
|
|
11
12
|
|
|
12
13
|
def __init__(self, options):
|
|
13
14
|
self.api_key = options["api_key"]
|
|
14
|
-
platform_url = options.get("platform_url", "
|
|
15
|
+
platform_url = options.get("platform_url", "http://localhost:3000")
|
|
15
16
|
|
|
16
17
|
self.platform_url = platform_url
|
|
17
18
|
self.debug = options.get("debug", False)
|
|
@@ -24,6 +25,10 @@ class BotVersionClient:
|
|
|
24
25
|
self._lock = threading.Lock()
|
|
25
26
|
atexit.register(self._flush)
|
|
26
27
|
|
|
28
|
+
# Per-endpoint send queue: updates for the same endpoint go out one after another
|
|
29
|
+
self._pending_updates = {}
|
|
30
|
+
self._pending_lock = threading.Lock()
|
|
31
|
+
|
|
27
32
|
# ── Register endpoints (batched) ─────────────────────────────────────────
|
|
28
33
|
|
|
29
34
|
def register_endpoints(self, endpoints):
|
|
@@ -73,18 +78,52 @@ class BotVersionClient:
|
|
|
73
78
|
# ── Update single endpoint (runtime interceptor) ─────────────────────────
|
|
74
79
|
|
|
75
80
|
def update_endpoint(self, endpoint):
|
|
81
|
+
payload = {
|
|
82
|
+
"workspaceKey": self.api_key,
|
|
83
|
+
"method": endpoint.get("method"),
|
|
84
|
+
"path": endpoint.get("path"),
|
|
85
|
+
"requestBody": endpoint.get("request_body"),
|
|
86
|
+
"responseBody": endpoint.get("response_body"),
|
|
87
|
+
"detectedBy": endpoint.get("detected_by", "runtime"),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Queued here, in the caller's thread, so updates for one endpoint always go out
|
|
91
|
+
# in the order they were requested (prevents request/reply ordering races).
|
|
92
|
+
key = f"{str(endpoint.get('method')).upper()}:{endpoint.get('path')}"
|
|
93
|
+
with self._pending_lock:
|
|
94
|
+
queue = self._pending_updates.get(key)
|
|
95
|
+
if queue is not None:
|
|
96
|
+
queue.append(payload) # a sender is already running for this endpoint
|
|
97
|
+
return
|
|
98
|
+
self._pending_updates[key] = deque([payload])
|
|
99
|
+
|
|
76
100
|
try:
|
|
77
|
-
self.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
threading.Thread(target=self._drain_updates, args=(key,), daemon=True).start()
|
|
102
|
+
except Exception:
|
|
103
|
+
# If no sender could start, clear the key so later updates are not stuck behind it
|
|
104
|
+
with self._pending_lock:
|
|
105
|
+
self._pending_updates.pop(key, None)
|
|
106
|
+
|
|
107
|
+
def _drain_updates(self, key):
|
|
108
|
+
# Sends queued updates for one endpoint, one after another, off the request path
|
|
109
|
+
while True:
|
|
110
|
+
with self._pending_lock:
|
|
111
|
+
queue = self._pending_updates.get(key)
|
|
112
|
+
if not queue:
|
|
113
|
+
self._pending_updates.pop(key, None)
|
|
114
|
+
return
|
|
115
|
+
next_payload = queue.popleft()
|
|
116
|
+
try:
|
|
117
|
+
self._post("/api/sdk/update-endpoint", next_payload)
|
|
118
|
+
if self.debug:
|
|
119
|
+
print(
|
|
120
|
+
f"[botversion] sent update {key} "
|
|
121
|
+
f"(request={bool(next_payload.get('requestBody'))}, "
|
|
122
|
+
f"response={bool(next_payload.get('responseBody'))})"
|
|
123
|
+
)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
if self.debug:
|
|
126
|
+
print(f"[botversion] update_endpoint failed: {e}")
|
|
88
127
|
|
|
89
128
|
|
|
90
129
|
# ── Register frontend route patterns ─────────────────────────────────────────
|