justanalytics-python 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.
@@ -0,0 +1,429 @@
1
+ """
2
+ JustAnalytics Python SDK — end-to-end observability for Python applications.
3
+
4
+ Provides traces, error capture, structured logging, and metrics ingestion
5
+ to the JustAnalytics platform (Datadog + Sentry + Google Analytics in One).
6
+
7
+ Quick Start::
8
+
9
+ import justanalytics
10
+
11
+ justanalytics.init(
12
+ site_id="site_abc123",
13
+ api_key="ja_sk_your_api_key_here",
14
+ service_name="api-server",
15
+ environment="production",
16
+ )
17
+
18
+ # Create traced spans
19
+ with justanalytics.start_span("process-order") as span:
20
+ span.set_attribute("order.id", "12345")
21
+ result = process_order()
22
+
23
+ # Capture exceptions
24
+ try:
25
+ risky_operation()
26
+ except Exception as e:
27
+ justanalytics.capture_exception(e, tags={"module": "payments"})
28
+
29
+ # Capture messages
30
+ justanalytics.capture_message("Deployment complete", level="info")
31
+
32
+ # Set user context
33
+ justanalytics.set_user(id="user-123", email="alice@example.com")
34
+
35
+ # Set tags
36
+ justanalytics.set_tag("feature", "checkout")
37
+
38
+ # Flush before shutdown
39
+ justanalytics.flush()
40
+ justanalytics.close()
41
+
42
+ Wire Protocol (all SDKs share the same endpoints)::
43
+
44
+ POST /api/ingest/spans — trace spans (batched array)
45
+ POST /api/ingest/errors — error events (individual)
46
+ POST /api/ingest/logs — log entries (batched array)
47
+ POST /api/ingest/metrics — infrastructure metrics (batched array)
48
+
49
+ Headers:
50
+ Authorization: Bearer ja_sk_...
51
+ Content-Type: application/json
52
+ X-Site-ID: <siteId>
53
+ User-Agent: justanalytics-python/0.1.0
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ __version__ = "0.1.0"
59
+
60
+ from contextlib import contextmanager
61
+ from typing import Any, Callable, Dict, Generator, List, Optional, TypeVar, Union
62
+
63
+ from .client import JustAnalyticsClient
64
+ from .context import get_active_span, get_tags, get_trace_id, get_user
65
+ from .span import Span
66
+ from .trace_context import TraceparentData, parse_traceparent, serialize_traceparent
67
+ from .types import (
68
+ ErrorLevel,
69
+ ErrorPayload,
70
+ LogLevel,
71
+ LogPayload,
72
+ MetricPayload,
73
+ SpanEvent,
74
+ SpanKind,
75
+ SpanPayload,
76
+ SpanStatus,
77
+ UserContext,
78
+ )
79
+
80
+ F = TypeVar("F", bound=Callable[..., Any])
81
+
82
+ # --- Singleton Client ---
83
+
84
+ _client = JustAnalyticsClient()
85
+
86
+
87
+ # --- Public API Functions ---
88
+
89
+
90
+ def init(
91
+ site_id: str,
92
+ api_key: str,
93
+ service_name: str,
94
+ environment: Optional[str] = None,
95
+ release: Optional[str] = None,
96
+ server_url: Optional[str] = None,
97
+ debug: bool = False,
98
+ flush_interval_s: float = 2.0,
99
+ max_batch_size: int = 100,
100
+ enabled: bool = True,
101
+ ) -> None:
102
+ """
103
+ Initialize the JustAnalytics SDK.
104
+
105
+ Must be called before any other SDK method. Calling ``init()`` more
106
+ than once logs a warning and is ignored.
107
+
108
+ Args:
109
+ site_id: Site ID from the JustAnalytics dashboard.
110
+ api_key: API key (format: ``ja_sk_...``).
111
+ service_name: Service name (e.g., "api-server", "worker").
112
+ environment: Deployment environment (e.g., "production", "staging").
113
+ release: Release/version string (e.g., "1.2.3", git SHA).
114
+ server_url: Base URL of JustAnalytics server.
115
+ debug: Enable debug logging.
116
+ flush_interval_s: Flush interval in seconds (default: 2.0).
117
+ max_batch_size: Max items per batch before immediate flush (default: 100).
118
+ enabled: Enable/disable the SDK (default: True).
119
+
120
+ Raises:
121
+ ValueError: If required fields are missing.
122
+
123
+ Example::
124
+
125
+ justanalytics.init(
126
+ site_id="site_abc123",
127
+ api_key="ja_sk_...",
128
+ service_name="api-server",
129
+ environment="production",
130
+ )
131
+ """
132
+ _client.init(
133
+ site_id=site_id,
134
+ api_key=api_key,
135
+ service_name=service_name,
136
+ environment=environment,
137
+ release=release,
138
+ server_url=server_url,
139
+ debug=debug,
140
+ flush_interval_s=flush_interval_s,
141
+ max_batch_size=max_batch_size,
142
+ enabled=enabled,
143
+ )
144
+
145
+
146
+ def is_initialized() -> bool:
147
+ """Whether ``init()`` has been called successfully."""
148
+ return _client.is_initialized
149
+
150
+
151
+ @contextmanager
152
+ def start_span(
153
+ name: str,
154
+ op: Optional[str] = None,
155
+ kind: SpanKind = SpanKind.INTERNAL,
156
+ attributes: Optional[Dict[str, Any]] = None,
157
+ ) -> Generator[Span, None, None]:
158
+ """
159
+ Create a span as a context manager.
160
+
161
+ The span is automatically ended when the ``with`` block exits.
162
+ If an exception occurs, the span status is set to ``error``.
163
+
164
+ Nested ``start_span()`` calls automatically form parent-child
165
+ relationships via ``contextvars``.
166
+
167
+ Args:
168
+ name: Operation name for the span.
169
+ op: Optional operation type.
170
+ kind: Span kind (default: INTERNAL).
171
+ attributes: Initial attributes.
172
+
173
+ Yields:
174
+ The created Span instance.
175
+
176
+ Example::
177
+
178
+ with justanalytics.start_span("fetch-user") as span:
179
+ span.set_attribute("user.id", user_id)
180
+ user = db.get_user(user_id)
181
+ """
182
+ with _client.start_span(name, op=op, kind=kind, attributes=attributes) as span:
183
+ yield span
184
+
185
+
186
+ def span(
187
+ name: Optional[str] = None,
188
+ op: Optional[str] = None,
189
+ kind: SpanKind = SpanKind.INTERNAL,
190
+ attributes: Optional[Dict[str, Any]] = None,
191
+ ) -> Callable[[F], F]:
192
+ """
193
+ Decorator that wraps a function in a span.
194
+
195
+ Args:
196
+ name: Span name (defaults to the function's qualified name).
197
+ op: Optional operation type.
198
+ kind: Span kind.
199
+ attributes: Initial span attributes.
200
+
201
+ Returns:
202
+ A decorator.
203
+
204
+ Example::
205
+
206
+ @justanalytics.span(op="db.query")
207
+ def get_user(user_id: str):
208
+ return db.query(...)
209
+ """
210
+ return _client.span_decorator(name=name, op=op, kind=kind, attributes=attributes)
211
+
212
+
213
+ def capture_exception(
214
+ error: BaseException,
215
+ tags: Optional[Dict[str, str]] = None,
216
+ extra: Optional[Dict[str, Any]] = None,
217
+ user: Optional[UserContext] = None,
218
+ level: Union[ErrorLevel, str] = ErrorLevel.ERROR,
219
+ fingerprint: Optional[List[str]] = None,
220
+ ) -> str:
221
+ """
222
+ Capture an exception and send it to JustAnalytics.
223
+
224
+ Automatically attaches the current trace_id, span_id, user context,
225
+ and tags from the contextvars context.
226
+
227
+ Args:
228
+ error: The exception to capture.
229
+ tags: Additional tags (merged with context tags).
230
+ extra: Arbitrary extra data.
231
+ user: User context (overrides context user).
232
+ level: Severity level (default: ERROR).
233
+ fingerprint: Custom fingerprint for grouping.
234
+
235
+ Returns:
236
+ A unique event_id, or empty string if SDK is disabled.
237
+
238
+ Example::
239
+
240
+ try:
241
+ risky_operation()
242
+ except Exception as e:
243
+ justanalytics.capture_exception(e, tags={"module": "payments"})
244
+ """
245
+ if isinstance(level, str):
246
+ level = ErrorLevel(level)
247
+ return _client.capture_exception(
248
+ error=error,
249
+ tags=tags,
250
+ extra=extra,
251
+ user=user,
252
+ level=level,
253
+ fingerprint=fingerprint,
254
+ )
255
+
256
+
257
+ def capture_message(
258
+ message: str,
259
+ level: Union[ErrorLevel, str] = ErrorLevel.INFO,
260
+ tags: Optional[Dict[str, str]] = None,
261
+ extra: Optional[Dict[str, Any]] = None,
262
+ ) -> str:
263
+ """
264
+ Capture a message and send it to JustAnalytics.
265
+
266
+ Args:
267
+ message: The message string.
268
+ level: Severity level (default: INFO).
269
+ tags: Additional tags.
270
+ extra: Arbitrary extra data.
271
+
272
+ Returns:
273
+ A unique event_id, or empty string if SDK is disabled.
274
+
275
+ Example::
276
+
277
+ justanalytics.capture_message(
278
+ "User exceeded rate limit",
279
+ level="warning",
280
+ tags={"userId": user.id},
281
+ )
282
+ """
283
+ if isinstance(level, str):
284
+ level = ErrorLevel(level)
285
+ return _client.capture_message(message=message, level=level, tags=tags, extra=extra)
286
+
287
+
288
+ def set_user(
289
+ id: Optional[str] = None,
290
+ email: Optional[str] = None,
291
+ username: Optional[str] = None,
292
+ ) -> None:
293
+ """
294
+ Set user context for the current execution scope.
295
+
296
+ Args:
297
+ id: User ID.
298
+ email: User email.
299
+ username: Username.
300
+
301
+ Example::
302
+
303
+ justanalytics.set_user(id="user-123", email="alice@example.com")
304
+ """
305
+ _client.set_user(id=id, email=email, username=username)
306
+
307
+
308
+ def set_tag(key: str, value: str) -> None:
309
+ """
310
+ Set a tag for the current execution scope.
311
+
312
+ Tags are attached as attributes on all spans created within
313
+ the current contextvars scope.
314
+
315
+ Args:
316
+ key: Tag key.
317
+ value: Tag value.
318
+
319
+ Example::
320
+
321
+ justanalytics.set_tag("feature", "checkout")
322
+ """
323
+ _client.set_tag(key, value)
324
+
325
+
326
+ def log(
327
+ level: Union[LogLevel, str],
328
+ message: str,
329
+ attributes: Optional[Dict[str, Any]] = None,
330
+ ) -> None:
331
+ """
332
+ Send a structured log entry to JustAnalytics.
333
+
334
+ Args:
335
+ level: Log severity level.
336
+ message: Log message.
337
+ attributes: Optional key-value metadata.
338
+
339
+ Example::
340
+
341
+ justanalytics.log("info", "User logged in", {"userId": "u123"})
342
+ """
343
+ if isinstance(level, str):
344
+ level = LogLevel(level)
345
+ _client.log(level=level, message=message, attributes=attributes)
346
+
347
+
348
+ def record_metric(
349
+ metric_name: str,
350
+ value: float,
351
+ tags: Optional[Dict[str, Any]] = None,
352
+ ) -> None:
353
+ """
354
+ Record a custom infrastructure metric.
355
+
356
+ Args:
357
+ metric_name: Metric name using dot notation.
358
+ value: Numeric value.
359
+ tags: Optional additional tags.
360
+
361
+ Example::
362
+
363
+ justanalytics.record_metric("custom.queue_size", 42, {"queue": "emails"})
364
+ """
365
+ _client.record_metric(metric_name=metric_name, value=value, tags=tags)
366
+
367
+
368
+ def flush() -> None:
369
+ """
370
+ Manually flush all pending data to the server.
371
+
372
+ Useful before a serverless function completes or in tests.
373
+ """
374
+ _client.flush()
375
+
376
+
377
+ def close() -> None:
378
+ """
379
+ Shut down the SDK: flush remaining data, stop timers.
380
+
381
+ After calling ``close()``, the SDK cannot be used again
382
+ until ``init()`` is called.
383
+ """
384
+ _client.close()
385
+
386
+
387
+ # --- Exported Names ---
388
+
389
+ __all__ = [
390
+ "__version__",
391
+ # Lifecycle
392
+ "init",
393
+ "is_initialized",
394
+ "flush",
395
+ "close",
396
+ # Spans
397
+ "start_span",
398
+ "span",
399
+ "get_active_span",
400
+ "get_trace_id",
401
+ # Errors
402
+ "capture_exception",
403
+ "capture_message",
404
+ # Context
405
+ "set_user",
406
+ "set_tag",
407
+ "get_user",
408
+ "get_tags",
409
+ # Logging
410
+ "log",
411
+ # Metrics
412
+ "record_metric",
413
+ # W3C Trace Context
414
+ "parse_traceparent",
415
+ "serialize_traceparent",
416
+ # Types
417
+ "Span",
418
+ "SpanKind",
419
+ "SpanStatus",
420
+ "SpanEvent",
421
+ "SpanPayload",
422
+ "ErrorLevel",
423
+ "ErrorPayload",
424
+ "LogLevel",
425
+ "LogPayload",
426
+ "MetricPayload",
427
+ "UserContext",
428
+ "TraceparentData",
429
+ ]