botversion-sdk 1.0.5__tar.gz → 1.1.6__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-1.0.5 → botversion_sdk-1.1.6}/PKG-INFO +1 -1
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/__init__.py +114 -7
- botversion_sdk-1.1.6/botversion_sdk/cli/__init__.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/client.py +16 -8
- botversion_sdk-1.1.6/botversion_sdk/interceptor.py +552 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/scanner.py +659 -32
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk.egg-info/PKG-INFO +1 -1
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk.egg-info/SOURCES.txt +1 -1
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/pyproject.toml +1 -1
- {botversion_sdk-1.0.5/botversion_sdk → botversion_sdk-1.1.6}/setup.py +1 -1
- botversion_sdk-1.0.5/botversion_sdk/interceptor.py +0 -257
- botversion_sdk-1.0.5/setup.py +0 -42
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/cli/detector.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/cli/generator.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/cli/init.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/cli/prompts.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk/cli/writer.py +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk.egg-info/dependency_links.txt +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk.egg-info/entry_points.txt +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/botversion_sdk.egg-info/top_level.txt +0 -0
- {botversion_sdk-1.0.5 → botversion_sdk-1.1.6}/setup.cfg +0 -0
|
@@ -10,6 +10,14 @@ from .interceptor import (
|
|
|
10
10
|
attach_fastapi_interceptor,
|
|
11
11
|
attach_flask_interceptor,
|
|
12
12
|
attach_django_interceptor,
|
|
13
|
+
attach_starlette_interceptor,
|
|
14
|
+
attach_sanic_interceptor,
|
|
15
|
+
attach_falcon_interceptor,
|
|
16
|
+
attach_bottle_interceptor,
|
|
17
|
+
attach_aiohttp_interceptor,
|
|
18
|
+
attach_tornado_interceptor,
|
|
19
|
+
attach_pyramid_interceptor,
|
|
20
|
+
attach_cherrypy_interceptor,
|
|
13
21
|
)
|
|
14
22
|
|
|
15
23
|
_initialized = False
|
|
@@ -55,6 +63,23 @@ def init(app=None, api_key=None, **options):
|
|
|
55
63
|
attach_flask_interceptor(app, _client, interceptor_options)
|
|
56
64
|
elif framework == "django":
|
|
57
65
|
attach_django_interceptor(_client, interceptor_options)
|
|
66
|
+
# ── NEW ───────────────────────────────────────────────────────
|
|
67
|
+
elif framework == "starlette":
|
|
68
|
+
attach_starlette_interceptor(app, _client, interceptor_options)
|
|
69
|
+
elif framework == "sanic":
|
|
70
|
+
attach_sanic_interceptor(app, _client, interceptor_options)
|
|
71
|
+
elif framework == "falcon":
|
|
72
|
+
attach_falcon_interceptor(app, _client, interceptor_options)
|
|
73
|
+
elif framework == "bottle":
|
|
74
|
+
attach_bottle_interceptor(app, _client, interceptor_options)
|
|
75
|
+
elif framework == "aiohttp":
|
|
76
|
+
attach_aiohttp_interceptor(app, _client, interceptor_options)
|
|
77
|
+
elif framework == "tornado":
|
|
78
|
+
attach_tornado_interceptor(app, _client, interceptor_options)
|
|
79
|
+
elif framework == "pyramid":
|
|
80
|
+
attach_pyramid_interceptor(app, _client, interceptor_options)
|
|
81
|
+
elif framework == "cherrypy":
|
|
82
|
+
attach_cherrypy_interceptor(app, _client, interceptor_options)
|
|
58
83
|
return
|
|
59
84
|
|
|
60
85
|
_initialized = True
|
|
@@ -96,6 +121,23 @@ def init(app=None, api_key=None, **options):
|
|
|
96
121
|
attach_flask_interceptor(app, _client, interceptor_options)
|
|
97
122
|
elif framework == "django":
|
|
98
123
|
attach_django_interceptor(_client, interceptor_options)
|
|
124
|
+
# ── NEW ───────────────────────────────────────────────────────────────────
|
|
125
|
+
elif framework == "starlette":
|
|
126
|
+
attach_starlette_interceptor(app, _client, interceptor_options)
|
|
127
|
+
elif framework == "sanic":
|
|
128
|
+
attach_sanic_interceptor(app, _client, interceptor_options)
|
|
129
|
+
elif framework == "falcon":
|
|
130
|
+
attach_falcon_interceptor(app, _client, interceptor_options)
|
|
131
|
+
elif framework == "bottle":
|
|
132
|
+
attach_bottle_interceptor(app, _client, interceptor_options)
|
|
133
|
+
elif framework == "aiohttp":
|
|
134
|
+
attach_aiohttp_interceptor(app, _client, interceptor_options)
|
|
135
|
+
elif framework == "tornado":
|
|
136
|
+
attach_tornado_interceptor(app, _client, interceptor_options)
|
|
137
|
+
elif framework == "pyramid":
|
|
138
|
+
attach_pyramid_interceptor(app, _client, interceptor_options)
|
|
139
|
+
elif framework == "cherrypy":
|
|
140
|
+
attach_cherrypy_interceptor(app, _client, interceptor_options)
|
|
99
141
|
else:
|
|
100
142
|
return
|
|
101
143
|
|
|
@@ -110,8 +152,8 @@ def init(app=None, api_key=None, **options):
|
|
|
110
152
|
elif framework == "django":
|
|
111
153
|
endpoints = scan_routes(None, "django")
|
|
112
154
|
|
|
113
|
-
|
|
114
|
-
|
|
155
|
+
# For any other framework with no app object, skip backend scan
|
|
156
|
+
# but still continue to frontend scan below
|
|
115
157
|
|
|
116
158
|
if endpoints:
|
|
117
159
|
_client.register_endpoints_now(endpoints)
|
|
@@ -127,31 +169,57 @@ def init(app=None, api_key=None, **options):
|
|
|
127
169
|
_client.register_route_patterns(route_patterns)
|
|
128
170
|
|
|
129
171
|
if framework == "flask":
|
|
172
|
+
with app.app_context():
|
|
173
|
+
t = threading.Thread(target=_run_scan, daemon=False)
|
|
174
|
+
t.start()
|
|
175
|
+
t.join(timeout=15)
|
|
176
|
+
app._botversion_scanned = True
|
|
177
|
+
|
|
130
178
|
@app.after_request
|
|
131
179
|
def _botversion_first_scan(response):
|
|
132
180
|
if not getattr(app, '_botversion_scanned', False):
|
|
133
181
|
app._botversion_scanned = True
|
|
134
|
-
threading.Thread(target=_run_scan, daemon=
|
|
182
|
+
t = threading.Thread(target=_run_scan, daemon=False)
|
|
183
|
+
t.start()
|
|
184
|
+
t.join(timeout=15)
|
|
135
185
|
return response
|
|
136
186
|
|
|
137
187
|
elif framework == "fastapi":
|
|
188
|
+
@app.on_event("startup")
|
|
189
|
+
async def _botversion_startup_scan():
|
|
190
|
+
if not getattr(app, '_botversion_scanned', False):
|
|
191
|
+
app._botversion_scanned = True
|
|
192
|
+
t = threading.Thread(target=_run_scan, daemon=False)
|
|
193
|
+
t.start()
|
|
194
|
+
t.join(timeout=15)
|
|
195
|
+
|
|
138
196
|
@app.middleware("http")
|
|
139
197
|
async def _botversion_first_scan(request, call_next):
|
|
140
198
|
if not getattr(app, '_botversion_scanned', False):
|
|
141
199
|
app._botversion_scanned = True
|
|
142
|
-
threading.Thread(target=_run_scan, daemon=
|
|
200
|
+
t = threading.Thread(target=_run_scan, daemon=False)
|
|
201
|
+
t.start()
|
|
202
|
+
t.join(timeout=15)
|
|
143
203
|
return await call_next(request)
|
|
144
204
|
|
|
145
205
|
elif framework == "django":
|
|
146
206
|
try:
|
|
147
207
|
from django.apps import apps
|
|
148
208
|
if apps.ready:
|
|
149
|
-
threading.Thread(target=_run_scan, daemon=
|
|
209
|
+
t = threading.Thread(target=_run_scan, daemon=False)
|
|
210
|
+
t.start()
|
|
211
|
+
t.join(timeout=15)
|
|
150
212
|
except Exception:
|
|
151
|
-
t = threading.Timer(
|
|
152
|
-
t.daemon =
|
|
213
|
+
t = threading.Timer(3.0, _run_scan)
|
|
214
|
+
t.daemon = False
|
|
153
215
|
t.start()
|
|
154
216
|
|
|
217
|
+
elif framework in ("starlette", "sanic", "falcon", "bottle",
|
|
218
|
+
"aiohttp", "tornado", "pyramid", "cherrypy"):
|
|
219
|
+
t = threading.Timer(3.0, _run_scan)
|
|
220
|
+
t.daemon = False
|
|
221
|
+
t.start()
|
|
222
|
+
|
|
155
223
|
|
|
156
224
|
def get_endpoints():
|
|
157
225
|
"""Get all registered endpoints for this workspace."""
|
|
@@ -172,11 +240,32 @@ def register_endpoint(endpoint):
|
|
|
172
240
|
def _detect_framework(app):
|
|
173
241
|
if app is not None:
|
|
174
242
|
app_type = type(app).__module__ + "." + type(app).__name__
|
|
243
|
+
|
|
244
|
+
# ── Existing ──────────────────────────────────────────────────────
|
|
175
245
|
if "fastapi" in app_type.lower():
|
|
176
246
|
return "fastapi"
|
|
177
247
|
if "flask" in app_type.lower():
|
|
178
248
|
return "flask"
|
|
179
249
|
|
|
250
|
+
# ── NEW ───────────────────────────────────────────────────────────
|
|
251
|
+
if "starlette" in app_type.lower():
|
|
252
|
+
return "starlette"
|
|
253
|
+
if "sanic" in app_type.lower():
|
|
254
|
+
return "sanic"
|
|
255
|
+
if "falcon" in app_type.lower():
|
|
256
|
+
return "falcon"
|
|
257
|
+
if "bottle" in app_type.lower():
|
|
258
|
+
return "bottle"
|
|
259
|
+
if "aiohttp" in app_type.lower():
|
|
260
|
+
return "aiohttp"
|
|
261
|
+
if "tornado" in app_type.lower():
|
|
262
|
+
return "tornado"
|
|
263
|
+
if "pyramid" in app_type.lower():
|
|
264
|
+
return "pyramid"
|
|
265
|
+
if "cherrypy" in app_type.lower():
|
|
266
|
+
return "cherrypy"
|
|
267
|
+
|
|
268
|
+
# ── No app object passed — try detecting from imported modules ────────
|
|
180
269
|
if app is None:
|
|
181
270
|
if "django" in sys.modules:
|
|
182
271
|
try:
|
|
@@ -186,11 +275,29 @@ def _detect_framework(app):
|
|
|
186
275
|
except Exception:
|
|
187
276
|
pass
|
|
188
277
|
|
|
278
|
+
# ── Fallback — check sys.modules ──────────────────────────────────────
|
|
189
279
|
if "fastapi" in sys.modules:
|
|
190
280
|
return "fastapi"
|
|
191
281
|
if "flask" in sys.modules:
|
|
192
282
|
return "flask"
|
|
193
283
|
if "django" in sys.modules:
|
|
194
284
|
return "django"
|
|
285
|
+
# ── NEW fallbacks ─────────────────────────────────────────────────────
|
|
286
|
+
if "starlette" in sys.modules:
|
|
287
|
+
return "starlette"
|
|
288
|
+
if "sanic" in sys.modules:
|
|
289
|
+
return "sanic"
|
|
290
|
+
if "falcon" in sys.modules:
|
|
291
|
+
return "falcon"
|
|
292
|
+
if "bottle" in sys.modules:
|
|
293
|
+
return "bottle"
|
|
294
|
+
if "aiohttp" in sys.modules:
|
|
295
|
+
return "aiohttp"
|
|
296
|
+
if "tornado" in sys.modules:
|
|
297
|
+
return "tornado"
|
|
298
|
+
if "pyramid" in sys.modules:
|
|
299
|
+
return "pyramid"
|
|
300
|
+
if "cherrypy" in sys.modules:
|
|
301
|
+
return "cherrypy"
|
|
195
302
|
|
|
196
303
|
return None
|
|
File without changes
|
|
@@ -47,8 +47,9 @@ class BotVersionClient:
|
|
|
47
47
|
"endpoints": endpoints,
|
|
48
48
|
})
|
|
49
49
|
return data
|
|
50
|
-
except Exception:
|
|
51
|
-
|
|
50
|
+
except Exception as e:
|
|
51
|
+
if self.debug:
|
|
52
|
+
print(f"[botversion] register_endpoints_now failed: {e}")
|
|
52
53
|
|
|
53
54
|
# ── Flush batch ──────────────────────────────────────────────────────────
|
|
54
55
|
|
|
@@ -65,8 +66,9 @@ class BotVersionClient:
|
|
|
65
66
|
"workspaceKey": self.api_key,
|
|
66
67
|
"endpoints": to_send,
|
|
67
68
|
})
|
|
68
|
-
except Exception:
|
|
69
|
-
|
|
69
|
+
except Exception as e:
|
|
70
|
+
if self.debug:
|
|
71
|
+
print(f"[botversion] flush failed: {e}")
|
|
70
72
|
|
|
71
73
|
# ── Update single endpoint (runtime interceptor) ─────────────────────────
|
|
72
74
|
|
|
@@ -80,8 +82,9 @@ class BotVersionClient:
|
|
|
80
82
|
"responseBody": endpoint.get("response_body"),
|
|
81
83
|
"detectedBy": endpoint.get("detected_by", "runtime"),
|
|
82
84
|
})
|
|
83
|
-
except Exception:
|
|
84
|
-
|
|
85
|
+
except Exception as e:
|
|
86
|
+
if self.debug:
|
|
87
|
+
print(f"[botversion] update_endpoint failed: {e}")
|
|
85
88
|
|
|
86
89
|
|
|
87
90
|
# ── Register frontend route patterns ─────────────────────────────────────────
|
|
@@ -94,8 +97,9 @@ class BotVersionClient:
|
|
|
94
97
|
"workspaceKey": self.api_key,
|
|
95
98
|
"patterns": patterns,
|
|
96
99
|
})
|
|
97
|
-
except Exception:
|
|
98
|
-
|
|
100
|
+
except Exception as e:
|
|
101
|
+
if self.debug:
|
|
102
|
+
print(f"[botversion] register_route_patterns failed: {e}")
|
|
99
103
|
|
|
100
104
|
# ── Get all endpoints ────────────────────────────────────────────────────
|
|
101
105
|
|
|
@@ -123,6 +127,8 @@ class BotVersionClient:
|
|
|
123
127
|
try:
|
|
124
128
|
with urllib.request.urlopen(req, timeout=self.timeout) as res:
|
|
125
129
|
response_data = res.read().decode("utf-8")
|
|
130
|
+
if not response_data or not response_data.strip():
|
|
131
|
+
return {}
|
|
126
132
|
return json.loads(response_data)
|
|
127
133
|
except urllib.error.HTTPError as e:
|
|
128
134
|
error_body = e.read().decode("utf-8")
|
|
@@ -149,6 +155,8 @@ class BotVersionClient:
|
|
|
149
155
|
try:
|
|
150
156
|
with urllib.request.urlopen(req, timeout=self.timeout) as res:
|
|
151
157
|
response_data = res.read().decode("utf-8")
|
|
158
|
+
if not response_data or not response_data.strip():
|
|
159
|
+
return {}
|
|
152
160
|
return json.loads(response_data)
|
|
153
161
|
except urllib.error.HTTPError as e:
|
|
154
162
|
error_body = e.read().decode("utf-8")
|