thinkingsdk 0.1.0__py3-none-any.whl
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.
- thinkingsdk/__init__.py +443 -0
- thinkingsdk/_version.py +12 -0
- thinkingsdk/async_instrumentation.py +366 -0
- thinkingsdk/auto_instrument.py +115 -0
- thinkingsdk/background_sender.py +387 -0
- thinkingsdk/config.py +158 -0
- thinkingsdk/config_loader.py +316 -0
- thinkingsdk/context.py +279 -0
- thinkingsdk/custom_events.py +363 -0
- thinkingsdk/enhanced_context.py +239 -0
- thinkingsdk/enhanced_queue.py +80 -0
- thinkingsdk/event_deduplicator.py +338 -0
- thinkingsdk/event_queue.py +116 -0
- thinkingsdk/exception_chain.py +301 -0
- thinkingsdk/instrumentation.py +1139 -0
- thinkingsdk/integrations/__init__.py +84 -0
- thinkingsdk/integrations/console.py +60 -0
- thinkingsdk/integrations/django.py +328 -0
- thinkingsdk/integrations/fastapi.py +434 -0
- thinkingsdk/integrations/flask.py +323 -0
- thinkingsdk/integrations/logging.py +173 -0
- thinkingsdk/integrations/middleware_base.py +102 -0
- thinkingsdk/integrations/psycopg2.py +243 -0
- thinkingsdk/integrations/pymongo.py +223 -0
- thinkingsdk/integrations/redis_integration.py +244 -0
- thinkingsdk/integrations/sqlalchemy.py +219 -0
- thinkingsdk/integrations/stdlib.py +138 -0
- thinkingsdk/performance_monitor.py +314 -0
- thinkingsdk/pii_scrubber.py +250 -0
- thinkingsdk/strategic_sampling.py +357 -0
- thinkingsdk/thinkingsdk.yaml +119 -0
- thinkingsdk/validate.py +487 -0
- thinkingsdk-0.1.0.dist-info/METADATA +165 -0
- thinkingsdk-0.1.0.dist-info/RECORD +38 -0
- thinkingsdk-0.1.0.dist-info/WHEEL +5 -0
- thinkingsdk-0.1.0.dist-info/entry_points.txt +2 -0
- thinkingsdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- thinkingsdk-0.1.0.dist-info/top_level.txt +1 -0
thinkingsdk/__init__.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# thinkingsdk/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Production-grade ThinkingSDK client. Usage inside user code:
|
|
4
|
+
|
|
5
|
+
import thinkingsdk as thinking
|
|
6
|
+
|
|
7
|
+
# Basic usage
|
|
8
|
+
thinking.start(api_key="sk_live_XXXX")
|
|
9
|
+
|
|
10
|
+
# Advanced usage with configuration
|
|
11
|
+
config = {
|
|
12
|
+
'instrumentation': {'sample_rate': 0.5, 'capture_returns': True},
|
|
13
|
+
'sender': {'batch_size': 100, 'retry_attempts': 5},
|
|
14
|
+
'queue': {'maxsize': 20000}
|
|
15
|
+
}
|
|
16
|
+
thinking.start(api_key="sk_live_XXXX", config=config)
|
|
17
|
+
|
|
18
|
+
# Get statistics
|
|
19
|
+
stats = thinking.get_stats()
|
|
20
|
+
|
|
21
|
+
# Clean shutdown
|
|
22
|
+
thinking.stop()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import logging
|
|
27
|
+
import atexit
|
|
28
|
+
from typing import Dict, Any, Optional, List
|
|
29
|
+
|
|
30
|
+
from ._version import __version__, __version_info__
|
|
31
|
+
from .instrumentation import RuntimeInstrumentation
|
|
32
|
+
from .background_sender import BackgroundSender
|
|
33
|
+
from .event_queue import EventQueue
|
|
34
|
+
from .config import Config
|
|
35
|
+
from .config_loader import ConfigLoader
|
|
36
|
+
from .context import context, set_context, clear_context, add_context
|
|
37
|
+
from .event_deduplicator import EventDeduplicator
|
|
38
|
+
from .pii_scrubber import PIIScrubber
|
|
39
|
+
from .custom_events import BreadcrumbTracker, CustomEventTracker, Timer
|
|
40
|
+
from .enhanced_queue import EnhancedEventQueue
|
|
41
|
+
|
|
42
|
+
# Module-level state
|
|
43
|
+
_instrumentation: Optional[RuntimeInstrumentation] = None
|
|
44
|
+
_sender: Optional[BackgroundSender] = None
|
|
45
|
+
_queue: Optional[EventQueue] = None
|
|
46
|
+
_config: Optional[Config] = None
|
|
47
|
+
_deduplicator: Optional[EventDeduplicator] = None
|
|
48
|
+
_pii_scrubber: Optional[PIIScrubber] = None
|
|
49
|
+
_breadcrumb_tracker: Optional[BreadcrumbTracker] = None
|
|
50
|
+
_custom_event_tracker: Optional[CustomEventTracker] = None
|
|
51
|
+
_integrations: Optional[List] = None
|
|
52
|
+
|
|
53
|
+
#TODO: rethink hard stopping exceptions for all methods (e.g. raise Exceptions)
|
|
54
|
+
# This is a production-grade SDK, so we want to avoid raising exceptions
|
|
55
|
+
# unless absolutely necessary. Most methods will log errors instead of raising.
|
|
56
|
+
# However, some critical methods like start() and stop() will raise if called incorrectly.
|
|
57
|
+
# This should not hinder normal usage, but rather help catch misconfigurations early.
|
|
58
|
+
|
|
59
|
+
def start(
|
|
60
|
+
api_key: Optional[str] = None,
|
|
61
|
+
server_url: Optional[str] = None,
|
|
62
|
+
config: Optional[Dict[str, Any]] = None,
|
|
63
|
+
config_file: Optional[str] = None,
|
|
64
|
+
enable_logging: Optional[bool] = None
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Start ThinkingSDK instrumentation and background sending.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
api_key: API key for authentication (overrides config file)
|
|
71
|
+
server_url: Base URL of the ThinkingSDK server (overrides config file)
|
|
72
|
+
config: Optional configuration dictionary (overrides config file)
|
|
73
|
+
config_file: Path to YAML config file (default: searches for thinkingsdk.yaml)
|
|
74
|
+
enable_logging: Enable logging for debugging (overrides config file)
|
|
75
|
+
|
|
76
|
+
Priority: function args > config dict > config file > defaults
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
RuntimeError: If SDK is already started
|
|
80
|
+
"""
|
|
81
|
+
global _instrumentation, _sender, _queue, _config
|
|
82
|
+
|
|
83
|
+
# Check for disable flag - this takes precedence over everything
|
|
84
|
+
if os.environ.get('THINKINGSDK_DISABLE_AUTO'):
|
|
85
|
+
if enable_logging:
|
|
86
|
+
logging.info("ThinkingSDK disabled by THINKINGSDK_DISABLE_AUTO environment variable")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if _sender is not None:
|
|
90
|
+
raise RuntimeError("ThinkingSDK is already started. Call stop() first.")
|
|
91
|
+
|
|
92
|
+
# Load configuration from YAML file
|
|
93
|
+
config_loader = ConfigLoader(config_file)
|
|
94
|
+
|
|
95
|
+
# Check if SDK is enabled
|
|
96
|
+
if not config_loader.is_enabled():
|
|
97
|
+
if enable_logging:
|
|
98
|
+
logging.debug("ThinkingSDK is disabled in configuration")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Build final configuration (priority: args > config dict > yaml file)
|
|
102
|
+
final_config = config_loader.config.copy()
|
|
103
|
+
if config:
|
|
104
|
+
# Merge user-provided config
|
|
105
|
+
for key, value in config.items():
|
|
106
|
+
if isinstance(value, dict) and key in final_config:
|
|
107
|
+
final_config[key].update(value)
|
|
108
|
+
else:
|
|
109
|
+
final_config[key] = value
|
|
110
|
+
|
|
111
|
+
# Override with function arguments if provided
|
|
112
|
+
if api_key is None:
|
|
113
|
+
api_key = config_loader.get_api_key()
|
|
114
|
+
if server_url is None:
|
|
115
|
+
server_url = config_loader.get("server_url", "https://api.thinkingsdk.ai")
|
|
116
|
+
if enable_logging is None:
|
|
117
|
+
enable_logging = config_loader.get("debug", False)
|
|
118
|
+
|
|
119
|
+
# Initialize configuration
|
|
120
|
+
_config = Config(final_config)
|
|
121
|
+
|
|
122
|
+
# Set up logging if requested
|
|
123
|
+
if enable_logging or _config.is_logging_enabled():
|
|
124
|
+
logging.basicConfig(
|
|
125
|
+
level=getattr(logging, _config.get_log_level().upper()),
|
|
126
|
+
format='%(asctime)s - ThinkingSDK - %(levelname)s - %(message)s'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
#TODO: is _queue or enhanced_queue both needed? which one should be used?
|
|
130
|
+
try:
|
|
131
|
+
# Create components
|
|
132
|
+
_queue = EventQueue(**_config.get_queue_config())
|
|
133
|
+
|
|
134
|
+
# Create PII scrubber if privacy is enabled
|
|
135
|
+
global _pii_scrubber
|
|
136
|
+
privacy_config = final_config.get('privacy', {})
|
|
137
|
+
if privacy_config.get('sanitize_data', True):
|
|
138
|
+
_pii_scrubber = PIIScrubber(privacy_config)
|
|
139
|
+
|
|
140
|
+
# Create deduplicator for efficiency
|
|
141
|
+
global _deduplicator
|
|
142
|
+
_deduplicator = EventDeduplicator(final_config.get('deduplication', {}))
|
|
143
|
+
|
|
144
|
+
# Create breadcrumb tracker
|
|
145
|
+
global _breadcrumb_tracker
|
|
146
|
+
_breadcrumb_tracker = BreadcrumbTracker(max_breadcrumbs=100)
|
|
147
|
+
|
|
148
|
+
# Setup semantic event integrations for breadcrumbs
|
|
149
|
+
global _integrations
|
|
150
|
+
_integrations = []
|
|
151
|
+
|
|
152
|
+
# Add standard library integration (HTTP, subprocess)
|
|
153
|
+
from .integrations.stdlib import StdlibIntegration
|
|
154
|
+
stdlib_integration = StdlibIntegration()
|
|
155
|
+
stdlib_integration.setup_once()
|
|
156
|
+
_integrations.append(stdlib_integration)
|
|
157
|
+
|
|
158
|
+
# Add logging integration
|
|
159
|
+
from .integrations.logging import LoggingIntegration
|
|
160
|
+
logging_integration = LoggingIntegration(level=logging.DEBUG)
|
|
161
|
+
logging_integration.setup_once()
|
|
162
|
+
_integrations.append(logging_integration)
|
|
163
|
+
|
|
164
|
+
# Add console integration (print statements)
|
|
165
|
+
from .integrations.console import ConsoleIntegration
|
|
166
|
+
console_integration = ConsoleIntegration(capture_print=True)
|
|
167
|
+
console_integration.setup_once()
|
|
168
|
+
_integrations.append(console_integration)
|
|
169
|
+
|
|
170
|
+
# Try to add database integrations if available
|
|
171
|
+
try:
|
|
172
|
+
import sqlalchemy
|
|
173
|
+
from .integrations.sqlalchemy import SQLAlchemyIntegration
|
|
174
|
+
sqlalchemy_integration = SQLAlchemyIntegration(capture_params=False)
|
|
175
|
+
sqlalchemy_integration.setup_once()
|
|
176
|
+
_integrations.append(sqlalchemy_integration)
|
|
177
|
+
except ImportError:
|
|
178
|
+
pass # SQLAlchemy not installed
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
import psycopg2
|
|
182
|
+
from .integrations.psycopg2 import Psycopg2Integration
|
|
183
|
+
psycopg2_integration = Psycopg2Integration(capture_params=False)
|
|
184
|
+
psycopg2_integration.setup_once()
|
|
185
|
+
_integrations.append(psycopg2_integration)
|
|
186
|
+
except ImportError:
|
|
187
|
+
pass # psycopg2 not installed
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
import pymongo
|
|
191
|
+
from .integrations.pymongo import PyMongoIntegration
|
|
192
|
+
pymongo_integration = PyMongoIntegration(sanitize_queries=True)
|
|
193
|
+
pymongo_integration.setup_once()
|
|
194
|
+
_integrations.append(pymongo_integration)
|
|
195
|
+
except (ImportError, Exception) as e:
|
|
196
|
+
if enable_logging:
|
|
197
|
+
logging.debug(f"Skipping pymongo integration: {e}")
|
|
198
|
+
pass # pymongo not installed or incompatible
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
import redis
|
|
202
|
+
from .integrations.redis_integration import RedisIntegration
|
|
203
|
+
redis_integration = RedisIntegration(max_data_size=100)
|
|
204
|
+
redis_integration.setup_once()
|
|
205
|
+
_integrations.append(redis_integration)
|
|
206
|
+
except ImportError:
|
|
207
|
+
pass # redis not installed
|
|
208
|
+
|
|
209
|
+
# Create custom event tracker
|
|
210
|
+
global _custom_event_tracker
|
|
211
|
+
_custom_event_tracker = CustomEventTracker(_queue, _breadcrumb_tracker)
|
|
212
|
+
|
|
213
|
+
# Create enhanced queue that uses deduplicator and PII scrubber
|
|
214
|
+
enhanced_queue = EnhancedEventQueue(_queue, _deduplicator, _pii_scrubber)
|
|
215
|
+
|
|
216
|
+
_instrumentation = RuntimeInstrumentation(enhanced_queue, _config.get_instrumentation_config())
|
|
217
|
+
_sender = BackgroundSender(enhanced_queue, api_key, server_url, _config.get_sender_config())
|
|
218
|
+
|
|
219
|
+
# Start instrumentation and background sender
|
|
220
|
+
_instrumentation.setup_hooks()
|
|
221
|
+
_sender.start()
|
|
222
|
+
|
|
223
|
+
# Register automatic cleanup on Python exit
|
|
224
|
+
atexit.register(_cleanup_on_exit)
|
|
225
|
+
|
|
226
|
+
if enable_logging or _config.is_logging_enabled():
|
|
227
|
+
logging.debug("ThinkingSDK started successfully")
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
# Clean up on failure
|
|
231
|
+
stop()
|
|
232
|
+
raise RuntimeError(f"Failed to start ThinkingSDK: {e}") from e
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _cleanup_on_exit() -> None:
|
|
236
|
+
"""
|
|
237
|
+
Automatic cleanup function called on Python exit.
|
|
238
|
+
Ensures events are flushed before the program terminates.
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
# Silently cleanup - no output to maintain transparency
|
|
242
|
+
# Only cleanup if SDK is still running
|
|
243
|
+
if _sender is not None and _instrumentation is not None:
|
|
244
|
+
if _config and (_config.is_logging_enabled()):
|
|
245
|
+
logging.debug("ThinkingSDK: Automatic cleanup on exit - flushing events...")
|
|
246
|
+
stop(timeout=3.0) # Shorter timeout for exit handler
|
|
247
|
+
if _config and (_config.is_logging_enabled()):
|
|
248
|
+
logging.debug("ThinkingSDK: Exit cleanup completed")
|
|
249
|
+
except Exception as e:
|
|
250
|
+
# Don't let exit handler exceptions crash the program
|
|
251
|
+
if _config and (_config.is_logging_enabled()):
|
|
252
|
+
logging.debug(f"ThinkingSDK: Exit cleanup failed: {e}")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def stop(timeout: float = 5.0) -> None:
|
|
256
|
+
"""
|
|
257
|
+
Stop ThinkingSDK instrumentation and background sending.
|
|
258
|
+
TODO: empty the queue gracefully before stopping
|
|
259
|
+
Args:
|
|
260
|
+
timeout: Maximum time to wait for graceful shutdown (seconds)
|
|
261
|
+
"""
|
|
262
|
+
global _instrumentation, _sender, _queue, _config
|
|
263
|
+
|
|
264
|
+
# Unregister exit handler to prevent duplicate cleanup
|
|
265
|
+
try:
|
|
266
|
+
atexit.unregister(_cleanup_on_exit)
|
|
267
|
+
except ValueError:
|
|
268
|
+
pass # Already unregistered
|
|
269
|
+
|
|
270
|
+
if _config and (_config.is_logging_enabled()):
|
|
271
|
+
logging.debug("Stopping ThinkingSDK...")
|
|
272
|
+
|
|
273
|
+
# Clean up instrumentation
|
|
274
|
+
if _instrumentation:
|
|
275
|
+
try:
|
|
276
|
+
_instrumentation.cleanup_hooks()
|
|
277
|
+
except Exception as e:
|
|
278
|
+
if _config and _config.is_logging_enabled():
|
|
279
|
+
logging.error(f"Error cleaning up instrumentation: {e}")
|
|
280
|
+
finally:
|
|
281
|
+
_instrumentation = None
|
|
282
|
+
|
|
283
|
+
# Stop background sender
|
|
284
|
+
if _sender:
|
|
285
|
+
try:
|
|
286
|
+
_sender.stop(timeout=timeout)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
if _config and _config.is_logging_enabled():
|
|
289
|
+
logging.error(f"Error stopping sender: {e}")
|
|
290
|
+
finally:
|
|
291
|
+
_sender = None
|
|
292
|
+
|
|
293
|
+
# Clear remaining state
|
|
294
|
+
_queue = None
|
|
295
|
+
_config = None
|
|
296
|
+
|
|
297
|
+
if _config and _config.is_logging_enabled():
|
|
298
|
+
logging.debug("ThinkingSDK stopped")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_stats() -> Dict[str, Any]:
|
|
302
|
+
"""
|
|
303
|
+
Get statistics about the current ThinkingSDK session.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Dictionary containing statistics from all components
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
RuntimeError: If SDK is not started
|
|
310
|
+
"""
|
|
311
|
+
if not _sender:
|
|
312
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
313
|
+
|
|
314
|
+
stats = {
|
|
315
|
+
'sdk_active': True,
|
|
316
|
+
'config': _config.to_dict() if _config else {},
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# Add component statistics
|
|
320
|
+
if _queue:
|
|
321
|
+
stats['queue'] = _queue.get_stats()
|
|
322
|
+
|
|
323
|
+
if _instrumentation:
|
|
324
|
+
stats['instrumentation'] = _instrumentation.get_stats()
|
|
325
|
+
|
|
326
|
+
if _sender:
|
|
327
|
+
stats['sender'] = _sender.get_stats()
|
|
328
|
+
|
|
329
|
+
return stats
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def is_active() -> bool:
|
|
333
|
+
"""
|
|
334
|
+
Check if ThinkingSDK is currently active.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
True if SDK is started and running
|
|
338
|
+
"""
|
|
339
|
+
return _sender is not None and _instrumentation is not None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def track_event(event_name: str, data: Optional[Dict[str, Any]] = None, level: str = "info") -> None:
|
|
343
|
+
"""
|
|
344
|
+
Track a custom business event.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
event_name: Name of the event (e.g., "payment_processed")
|
|
348
|
+
data: Event data/metadata
|
|
349
|
+
level: Event level (debug, info, warning, error, critical)
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
RuntimeError: If SDK is not started
|
|
353
|
+
"""
|
|
354
|
+
if not _custom_event_tracker:
|
|
355
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
356
|
+
|
|
357
|
+
_custom_event_tracker.track_event(event_name, data, level)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def track_metric(metric_name: str, value: float, unit: str = "none", tags: Optional[Dict[str, str]] = None) -> None:
|
|
361
|
+
"""
|
|
362
|
+
Track a numeric metric.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
metric_name: Name of the metric
|
|
366
|
+
value: Numeric value
|
|
367
|
+
unit: Unit of measurement
|
|
368
|
+
tags: Additional tags
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
RuntimeError: If SDK is not started
|
|
372
|
+
"""
|
|
373
|
+
if not _custom_event_tracker:
|
|
374
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
375
|
+
|
|
376
|
+
_custom_event_tracker.track_metric(metric_name, value, unit, tags)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def add_breadcrumb(message: str, category: str = "default", level: str = "info", data: Optional[Dict[str, Any]] = None) -> None:
|
|
380
|
+
"""
|
|
381
|
+
Add a breadcrumb to the trail.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
message: Breadcrumb message
|
|
385
|
+
category: Category (navigation, http, console, user, etc.)
|
|
386
|
+
level: Severity level
|
|
387
|
+
data: Additional data
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
RuntimeError: If SDK is not started
|
|
391
|
+
"""
|
|
392
|
+
if not _custom_event_tracker:
|
|
393
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
394
|
+
|
|
395
|
+
_custom_event_tracker.add_breadcrumb(message, category, level, data)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def mark_feature_usage(feature_name: str, metadata: Optional[Dict[str, Any]] = None) -> None:
|
|
399
|
+
"""
|
|
400
|
+
Track feature usage for product analytics.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
feature_name: Name of the feature
|
|
404
|
+
metadata: Additional metadata
|
|
405
|
+
|
|
406
|
+
Raises:
|
|
407
|
+
RuntimeError: If SDK is not started
|
|
408
|
+
"""
|
|
409
|
+
if not _custom_event_tracker:
|
|
410
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
411
|
+
|
|
412
|
+
_custom_event_tracker.mark_feature_usage(feature_name, metadata)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def timer(operation_name: str, tags: Optional[Dict[str, str]] = None):
|
|
416
|
+
"""
|
|
417
|
+
Create a timer context manager for timing operations.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
operation_name: Name of the operation to time
|
|
421
|
+
tags: Additional tags
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Timer context manager
|
|
425
|
+
|
|
426
|
+
Example:
|
|
427
|
+
with thinking.timer("database_query"):
|
|
428
|
+
results = db.query("SELECT * FROM users")
|
|
429
|
+
"""
|
|
430
|
+
if not _custom_event_tracker:
|
|
431
|
+
raise RuntimeError("ThinkingSDK is not started. Call start() first.")
|
|
432
|
+
|
|
433
|
+
return Timer(_custom_event_tracker, operation_name, tags)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# Expose key classes for advanced usage
|
|
437
|
+
__all__ = [
|
|
438
|
+
'__version__', '__version_info__',
|
|
439
|
+
'start', 'stop', 'get_stats', 'is_active',
|
|
440
|
+
'context', 'set_context', 'clear_context', 'add_context',
|
|
441
|
+
'track_event', 'track_metric', 'add_breadcrumb', 'mark_feature_usage', 'timer',
|
|
442
|
+
'RuntimeInstrumentation', 'BackgroundSender', 'EventQueue', 'Config'
|
|
443
|
+
]
|
thinkingsdk/_version.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Version information for ThinkingSDK client."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__version_info__ = (0, 1, 0)
|
|
5
|
+
|
|
6
|
+
def get_version():
|
|
7
|
+
"""Return the version string."""
|
|
8
|
+
return __version__
|
|
9
|
+
|
|
10
|
+
def get_version_info():
|
|
11
|
+
"""Return version as a tuple of integers."""
|
|
12
|
+
return __version_info__
|