botversion-sdk 1.0.1__tar.gz → 1.0.3__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.3/PKG-INFO +14 -0
- botversion_sdk-1.0.3/botversion_sdk/__init__.py +226 -0
- botversion_sdk-1.0.3/botversion_sdk/cli/detector.py +1156 -0
- botversion_sdk-1.0.3/botversion_sdk/cli/generator.py +169 -0
- botversion_sdk-1.0.3/botversion_sdk/cli/init.py +673 -0
- botversion_sdk-1.0.3/botversion_sdk/cli/prompts.py +155 -0
- botversion_sdk-1.0.3/botversion_sdk/cli/writer.py +724 -0
- botversion_sdk-1.0.3/botversion_sdk/client.py +319 -0
- botversion_sdk-1.0.3/botversion_sdk/interceptor.py +396 -0
- botversion_sdk-1.0.3/botversion_sdk/scanner.py +1033 -0
- botversion_sdk-1.0.3/botversion_sdk/setup.py +42 -0
- botversion_sdk-1.0.3/botversion_sdk.egg-info/PKG-INFO +14 -0
- botversion_sdk-1.0.3/botversion_sdk.egg-info/SOURCES.txt +18 -0
- botversion_sdk-1.0.3/botversion_sdk.egg-info/requires.txt +1 -0
- botversion_sdk-1.0.3/botversion_sdk.egg-info/top_level.txt +1 -0
- {botversion_sdk-1.0.1 → botversion_sdk-1.0.3}/pyproject.toml +4 -2
- botversion_sdk-1.0.3/setup.py +42 -0
- botversion_sdk-1.0.1/PKG-INFO +0 -6
- botversion_sdk-1.0.1/botversion_sdk.egg-info/PKG-INFO +0 -6
- botversion_sdk-1.0.1/botversion_sdk.egg-info/SOURCES.txt +0 -6
- botversion_sdk-1.0.1/botversion_sdk.egg-info/top_level.txt +0 -1
- {botversion_sdk-1.0.1 → botversion_sdk-1.0.3}/botversion_sdk.egg-info/dependency_links.txt +0 -0
- {botversion_sdk-1.0.1 → botversion_sdk-1.0.3}/botversion_sdk.egg-info/entry_points.txt +0 -0
- {botversion_sdk-1.0.1 → botversion_sdk-1.0.3}/setup.cfg +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: botversion-sdk
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: BotVersion AI Agent SDK for FastAPI, Flask, and Django
|
|
5
|
+
Home-page: https://github.com/botversion/botversion-sdk-python
|
|
6
|
+
Author: BotVersion
|
|
7
|
+
Author-email: Saurav Dhakal <sauravdhakal828@gmail.com>
|
|
8
|
+
Requires-Python: >=3.7
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: websocket-client>=1.0.0
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# botversion-sdk-python/botversion-sdk/__init__.py
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
import builtins
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from .client import BotVersionClient
|
|
8
|
+
from .scanner import scan_routes, scan_frontend_routes
|
|
9
|
+
from .interceptor import (
|
|
10
|
+
attach_fastapi_interceptor,
|
|
11
|
+
attach_flask_interceptor,
|
|
12
|
+
attach_django_interceptor,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_initialized = False
|
|
16
|
+
_client = None
|
|
17
|
+
_options = {}
|
|
18
|
+
_app = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def init(app=None, api_key=None, **options):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the BotVersion SDK.
|
|
24
|
+
|
|
25
|
+
Works for FastAPI, Flask, and Django — auto-detects the framework.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
# FastAPI
|
|
29
|
+
botversion_sdk.init(app, api_key="YOUR_KEY")
|
|
30
|
+
|
|
31
|
+
# Flask
|
|
32
|
+
botversion_sdk.init(app, api_key="YOUR_KEY")
|
|
33
|
+
|
|
34
|
+
# Django — no app object needed:
|
|
35
|
+
botversion_sdk.init(api_key="YOUR_KEY")
|
|
36
|
+
"""
|
|
37
|
+
global _initialized, _client, _options, _app
|
|
38
|
+
|
|
39
|
+
if not api_key:
|
|
40
|
+
print("[BotVersion SDK] ❌ api_key is required.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Restore from builtins if module was re-imported after hot reload
|
|
44
|
+
if getattr(builtins, "_botversion_client", None):
|
|
45
|
+
_client = builtins._botversion_client
|
|
46
|
+
_options = builtins._botversion_options
|
|
47
|
+
_initialized = True
|
|
48
|
+
print("[BotVersion SDK] Restored from builtins — skipping re-init")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if _initialized:
|
|
52
|
+
print("[BotVersion SDK] ⚠ Already initialized — skipping")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
_initialized = True
|
|
56
|
+
_options = dict(options)
|
|
57
|
+
_options["api_key"] = api_key
|
|
58
|
+
_app = app
|
|
59
|
+
|
|
60
|
+
debug = options.get("debug", False)
|
|
61
|
+
|
|
62
|
+
# ── Auto-detect framework ─────────────────────────────────────────────────
|
|
63
|
+
framework = _detect_framework(app)
|
|
64
|
+
|
|
65
|
+
if not framework:
|
|
66
|
+
print("[BotVersion SDK] ❌ Could not detect framework.")
|
|
67
|
+
print("[BotVersion SDK] ❌ Make sure FastAPI, Flask, or Django is installed.")
|
|
68
|
+
_initialized = False
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
_client = BotVersionClient({
|
|
72
|
+
"api_key": api_key,
|
|
73
|
+
"platform_url": options.get("platform_url", "http://localhost:3000"),
|
|
74
|
+
"debug": debug,
|
|
75
|
+
"timeout": options.get("timeout", 30),
|
|
76
|
+
"flush_delay": options.get("flush_delay", 3),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
# Store globally so hot-reload can restore state
|
|
80
|
+
builtins._botversion_client = _client
|
|
81
|
+
builtins._botversion_options = _options
|
|
82
|
+
|
|
83
|
+
if debug:
|
|
84
|
+
print(f"[BotVersion SDK] ✅ Framework detected: {framework}")
|
|
85
|
+
|
|
86
|
+
interceptor_options = {
|
|
87
|
+
"exclude": options.get("exclude", []),
|
|
88
|
+
"api_prefix": options.get("api_prefix", None),
|
|
89
|
+
"debug": debug,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# ── Attach runtime interceptor ───────────────────────────────────────────
|
|
93
|
+
if framework == "fastapi":
|
|
94
|
+
attach_fastapi_interceptor(app, _client, interceptor_options)
|
|
95
|
+
elif framework == "flask":
|
|
96
|
+
attach_flask_interceptor(app, _client, interceptor_options)
|
|
97
|
+
elif framework == "django":
|
|
98
|
+
attach_django_interceptor(_client, interceptor_options)
|
|
99
|
+
else:
|
|
100
|
+
print(f"[BotVersion SDK] ❌ Unsupported framework: {framework}")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if debug:
|
|
104
|
+
print("[BotVersion SDK] ✅ Runtime interceptor attached")
|
|
105
|
+
|
|
106
|
+
# ── Static scan (delayed 500ms — let app finish registering routes) ──────
|
|
107
|
+
def _run_scan():
|
|
108
|
+
try:
|
|
109
|
+
endpoints = []
|
|
110
|
+
|
|
111
|
+
if app is not None:
|
|
112
|
+
print(f"[BotVersion SDK] Scanning {framework} routes...")
|
|
113
|
+
endpoints = scan_routes(app, framework)
|
|
114
|
+
print(f"[BotVersion SDK] Found {len(endpoints)} {framework} routes")
|
|
115
|
+
|
|
116
|
+
if debug:
|
|
117
|
+
import json
|
|
118
|
+
print(f"[BotVersion SDK] Endpoints: {json.dumps(endpoints, indent=2)}")
|
|
119
|
+
|
|
120
|
+
if len(endpoints) == 0:
|
|
121
|
+
print("[BotVersion SDK] ⚠ No endpoints found.")
|
|
122
|
+
print("[BotVersion SDK] ⚠ Make sure routes are defined BEFORE botversion_sdk.init()")
|
|
123
|
+
|
|
124
|
+
elif framework == "django":
|
|
125
|
+
print("[BotVersion SDK] Scanning Django routes...")
|
|
126
|
+
endpoints = scan_routes(None, "django")
|
|
127
|
+
print(f"[BotVersion SDK] Found {len(endpoints)} Django routes")
|
|
128
|
+
|
|
129
|
+
if len(endpoints) == 0:
|
|
130
|
+
print("[BotVersion SDK] ⚠ No Django routes found.")
|
|
131
|
+
print("[BotVersion SDK] ⚠ Make sure botversion_sdk.init() is called AFTER Django is fully loaded.")
|
|
132
|
+
else:
|
|
133
|
+
print("[BotVersion SDK] ❌ No routes to scan.")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if endpoints:
|
|
137
|
+
print(f"[BotVersion SDK] Sending {len(endpoints)} endpoints to platform...")
|
|
138
|
+
_client.register_endpoints_now(endpoints)
|
|
139
|
+
print(f"[BotVersion SDK] ✅ Static scan complete — {len(endpoints)} endpoints registered")
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"[BotVersion SDK] ❌ Scan error: {e}")
|
|
143
|
+
if debug:
|
|
144
|
+
import traceback
|
|
145
|
+
traceback.print_exc()
|
|
146
|
+
|
|
147
|
+
cwd = options.get("cwd", os.getcwd())
|
|
148
|
+
route_patterns = scan_frontend_routes(cwd)
|
|
149
|
+
if route_patterns:
|
|
150
|
+
print(f"[BotVersion SDK] Found {len(route_patterns)} frontend route patterns")
|
|
151
|
+
_client.register_route_patterns(route_patterns)
|
|
152
|
+
print("[BotVersion SDK] ✅ Route patterns registered")
|
|
153
|
+
|
|
154
|
+
print("[BotVersion SDK] ✅ Initialization complete")
|
|
155
|
+
|
|
156
|
+
# ── Connect WebSocket immediately — don't wait for first request ──────
|
|
157
|
+
_client.connect()
|
|
158
|
+
|
|
159
|
+
if framework == "flask":
|
|
160
|
+
@app.after_request
|
|
161
|
+
def _botversion_first_scan(response):
|
|
162
|
+
if not getattr(app, '_botversion_scanned', False):
|
|
163
|
+
app._botversion_scanned = True
|
|
164
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
165
|
+
return response
|
|
166
|
+
|
|
167
|
+
elif framework == "fastapi":
|
|
168
|
+
@app.middleware("http")
|
|
169
|
+
async def _botversion_first_scan(request, call_next):
|
|
170
|
+
if not getattr(app, '_botversion_scanned', False):
|
|
171
|
+
app._botversion_scanned = True
|
|
172
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
173
|
+
return await call_next(request)
|
|
174
|
+
|
|
175
|
+
elif framework == "django":
|
|
176
|
+
try:
|
|
177
|
+
from django.apps import apps
|
|
178
|
+
if apps.ready:
|
|
179
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
180
|
+
except Exception:
|
|
181
|
+
t = threading.Timer(2.0, _run_scan)
|
|
182
|
+
t.daemon = True
|
|
183
|
+
t.start()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_endpoints():
|
|
187
|
+
"""Get all registered endpoints for this workspace."""
|
|
188
|
+
if not _client:
|
|
189
|
+
raise RuntimeError("BotVersion SDK not initialized. Call botversion_sdk.init() first.")
|
|
190
|
+
return _client.get_endpoints()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def register_endpoint(endpoint):
|
|
194
|
+
"""Manually register a single endpoint."""
|
|
195
|
+
if not _client:
|
|
196
|
+
raise RuntimeError("BotVersion SDK not initialized.")
|
|
197
|
+
return _client.register_endpoints([endpoint])
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ── Framework auto-detection ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
def _detect_framework(app):
|
|
203
|
+
if app is not None:
|
|
204
|
+
app_type = type(app).__module__ + "." + type(app).__name__
|
|
205
|
+
if "fastapi" in app_type.lower():
|
|
206
|
+
return "fastapi"
|
|
207
|
+
if "flask" in app_type.lower():
|
|
208
|
+
return "flask"
|
|
209
|
+
|
|
210
|
+
if app is None:
|
|
211
|
+
if "django" in sys.modules:
|
|
212
|
+
try:
|
|
213
|
+
from django.conf import settings
|
|
214
|
+
if settings.configured:
|
|
215
|
+
return "django"
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
if "fastapi" in sys.modules:
|
|
220
|
+
return "fastapi"
|
|
221
|
+
if "flask" in sys.modules:
|
|
222
|
+
return "flask"
|
|
223
|
+
if "django" in sys.modules:
|
|
224
|
+
return "django"
|
|
225
|
+
|
|
226
|
+
return None
|