botversion-sdk 1.0.2__tar.gz → 1.0.4__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.4/PKG-INFO +13 -0
- botversion_sdk-1.0.4/botversion_sdk/__init__.py +196 -0
- botversion_sdk-1.0.4/botversion_sdk/cli/detector.py +1157 -0
- botversion_sdk-1.0.4/botversion_sdk/cli/generator.py +169 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/init.py +184 -166
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/prompts.py +34 -55
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/writer.py +297 -103
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/client.py +41 -91
- botversion_sdk-1.0.4/botversion_sdk/interceptor.py +257 -0
- botversion_sdk-1.0.4/botversion_sdk/scanner.py +999 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/setup.py +1 -1
- botversion_sdk-1.0.4/botversion_sdk.egg-info/PKG-INFO +13 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/SOURCES.txt +1 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/pyproject.toml +1 -1
- botversion_sdk-1.0.4/setup.py +42 -0
- botversion_sdk-1.0.2/PKG-INFO +0 -6
- botversion_sdk-1.0.2/botversion_sdk/__init__.py +0 -391
- botversion_sdk-1.0.2/botversion_sdk/cli/detector.py +0 -852
- botversion_sdk-1.0.2/botversion_sdk/cli/generator.py +0 -296
- botversion_sdk-1.0.2/botversion_sdk/interceptor.py +0 -589
- botversion_sdk-1.0.2/botversion_sdk/scanner.py +0 -253
- botversion_sdk-1.0.2/botversion_sdk.egg-info/PKG-INFO +0 -6
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/dependency_links.txt +0 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/entry_points.txt +0 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/top_level.txt +0 -0
- {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/setup.cfg +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: botversion-sdk
|
|
3
|
+
Version: 1.0.4
|
|
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
|
+
Dynamic: author
|
|
11
|
+
Dynamic: description-content-type
|
|
12
|
+
Dynamic: home-page
|
|
13
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
# Restore from builtins if module was re-imported after hot reload
|
|
40
|
+
if getattr(builtins, "_botversion_client", None):
|
|
41
|
+
_client = builtins._botversion_client
|
|
42
|
+
_options = builtins._botversion_options
|
|
43
|
+
_initialized = True
|
|
44
|
+
# Re-attach interceptor after hot reload
|
|
45
|
+
framework = _detect_framework(app)
|
|
46
|
+
if framework and _client:
|
|
47
|
+
interceptor_options = {
|
|
48
|
+
"exclude": _options.get("exclude", []),
|
|
49
|
+
"api_prefix": _options.get("api_prefix", None),
|
|
50
|
+
"debug": _options.get("debug", False),
|
|
51
|
+
}
|
|
52
|
+
if framework == "fastapi":
|
|
53
|
+
attach_fastapi_interceptor(app, _client, interceptor_options)
|
|
54
|
+
elif framework == "flask":
|
|
55
|
+
attach_flask_interceptor(app, _client, interceptor_options)
|
|
56
|
+
elif framework == "django":
|
|
57
|
+
attach_django_interceptor(_client, interceptor_options)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
_initialized = True
|
|
61
|
+
_options = dict(options)
|
|
62
|
+
_options["api_key"] = api_key
|
|
63
|
+
_app = app
|
|
64
|
+
|
|
65
|
+
debug = options.get("debug", False)
|
|
66
|
+
|
|
67
|
+
# ── Auto-detect framework ─────────────────────────────────────────────────
|
|
68
|
+
framework = _detect_framework(app)
|
|
69
|
+
|
|
70
|
+
if not framework:
|
|
71
|
+
_initialized = False
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
_client = BotVersionClient({
|
|
75
|
+
"api_key": api_key,
|
|
76
|
+
"platform_url": options.get("platform_url", "https://botversion.com"),
|
|
77
|
+
"debug": debug,
|
|
78
|
+
"timeout": options.get("timeout", 30),
|
|
79
|
+
"flush_delay": options.get("flush_delay", 3),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
# Store globally so hot-reload can restore state
|
|
83
|
+
builtins._botversion_client = _client
|
|
84
|
+
builtins._botversion_options = _options
|
|
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
|
+
return
|
|
101
|
+
|
|
102
|
+
# ── Static scan (delayed 500ms — let app finish registering routes) ──────
|
|
103
|
+
def _run_scan():
|
|
104
|
+
try:
|
|
105
|
+
endpoints = []
|
|
106
|
+
|
|
107
|
+
if app is not None:
|
|
108
|
+
endpoints = scan_routes(app, framework)
|
|
109
|
+
|
|
110
|
+
elif framework == "django":
|
|
111
|
+
endpoints = scan_routes(None, "django")
|
|
112
|
+
|
|
113
|
+
else:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if endpoints:
|
|
117
|
+
_client.register_endpoints_now(endpoints)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
if debug:
|
|
121
|
+
import traceback
|
|
122
|
+
traceback.print_exc()
|
|
123
|
+
|
|
124
|
+
cwd = options.get("cwd", os.getcwd())
|
|
125
|
+
route_patterns = scan_frontend_routes(cwd)
|
|
126
|
+
if route_patterns:
|
|
127
|
+
_client.register_route_patterns(route_patterns)
|
|
128
|
+
|
|
129
|
+
if framework == "flask":
|
|
130
|
+
@app.after_request
|
|
131
|
+
def _botversion_first_scan(response):
|
|
132
|
+
if not getattr(app, '_botversion_scanned', False):
|
|
133
|
+
app._botversion_scanned = True
|
|
134
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
135
|
+
return response
|
|
136
|
+
|
|
137
|
+
elif framework == "fastapi":
|
|
138
|
+
@app.middleware("http")
|
|
139
|
+
async def _botversion_first_scan(request, call_next):
|
|
140
|
+
if not getattr(app, '_botversion_scanned', False):
|
|
141
|
+
app._botversion_scanned = True
|
|
142
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
143
|
+
return await call_next(request)
|
|
144
|
+
|
|
145
|
+
elif framework == "django":
|
|
146
|
+
try:
|
|
147
|
+
from django.apps import apps
|
|
148
|
+
if apps.ready:
|
|
149
|
+
threading.Thread(target=_run_scan, daemon=True).start()
|
|
150
|
+
except Exception:
|
|
151
|
+
t = threading.Timer(2.0, _run_scan)
|
|
152
|
+
t.daemon = True
|
|
153
|
+
t.start()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_endpoints():
|
|
157
|
+
"""Get all registered endpoints for this workspace."""
|
|
158
|
+
if not _client:
|
|
159
|
+
raise RuntimeError("BotVersion SDK not initialized. Call botversion_sdk.init() first.")
|
|
160
|
+
return _client.get_endpoints()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def register_endpoint(endpoint):
|
|
164
|
+
"""Manually register a single endpoint."""
|
|
165
|
+
if not _client:
|
|
166
|
+
raise RuntimeError("BotVersion SDK not initialized.")
|
|
167
|
+
return _client.register_endpoints([endpoint])
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ── Framework auto-detection ─────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
def _detect_framework(app):
|
|
173
|
+
if app is not None:
|
|
174
|
+
app_type = type(app).__module__ + "." + type(app).__name__
|
|
175
|
+
if "fastapi" in app_type.lower():
|
|
176
|
+
return "fastapi"
|
|
177
|
+
if "flask" in app_type.lower():
|
|
178
|
+
return "flask"
|
|
179
|
+
|
|
180
|
+
if app is None:
|
|
181
|
+
if "django" in sys.modules:
|
|
182
|
+
try:
|
|
183
|
+
from django.conf import settings
|
|
184
|
+
if settings.configured:
|
|
185
|
+
return "django"
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
if "fastapi" in sys.modules:
|
|
190
|
+
return "fastapi"
|
|
191
|
+
if "flask" in sys.modules:
|
|
192
|
+
return "flask"
|
|
193
|
+
if "django" in sys.modules:
|
|
194
|
+
return "django"
|
|
195
|
+
|
|
196
|
+
return None
|