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.
Files changed (26) hide show
  1. botversion_sdk-1.0.4/PKG-INFO +13 -0
  2. botversion_sdk-1.0.4/botversion_sdk/__init__.py +196 -0
  3. botversion_sdk-1.0.4/botversion_sdk/cli/detector.py +1157 -0
  4. botversion_sdk-1.0.4/botversion_sdk/cli/generator.py +169 -0
  5. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/init.py +184 -166
  6. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/prompts.py +34 -55
  7. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/cli/writer.py +297 -103
  8. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/client.py +41 -91
  9. botversion_sdk-1.0.4/botversion_sdk/interceptor.py +257 -0
  10. botversion_sdk-1.0.4/botversion_sdk/scanner.py +999 -0
  11. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk/setup.py +1 -1
  12. botversion_sdk-1.0.4/botversion_sdk.egg-info/PKG-INFO +13 -0
  13. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/SOURCES.txt +1 -0
  14. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/pyproject.toml +1 -1
  15. botversion_sdk-1.0.4/setup.py +42 -0
  16. botversion_sdk-1.0.2/PKG-INFO +0 -6
  17. botversion_sdk-1.0.2/botversion_sdk/__init__.py +0 -391
  18. botversion_sdk-1.0.2/botversion_sdk/cli/detector.py +0 -852
  19. botversion_sdk-1.0.2/botversion_sdk/cli/generator.py +0 -296
  20. botversion_sdk-1.0.2/botversion_sdk/interceptor.py +0 -589
  21. botversion_sdk-1.0.2/botversion_sdk/scanner.py +0 -253
  22. botversion_sdk-1.0.2/botversion_sdk.egg-info/PKG-INFO +0 -6
  23. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/dependency_links.txt +0 -0
  24. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/entry_points.txt +0 -0
  25. {botversion_sdk-1.0.2 → botversion_sdk-1.0.4}/botversion_sdk.egg-info/top_level.txt +0 -0
  26. {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