tusk-drift-python-sdk 0.1.0__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 (46) hide show
  1. tusk_drift_python_sdk-0.1.0/MANIFEST.in +5 -0
  2. tusk_drift_python_sdk-0.1.0/PKG-INFO +83 -0
  3. tusk_drift_python_sdk-0.1.0/README.md +45 -0
  4. tusk_drift_python_sdk-0.1.0/drift/__init__.py +68 -0
  5. tusk_drift_python_sdk-0.1.0/drift/core/__init__.py +66 -0
  6. tusk_drift_python_sdk-0.1.0/drift/core/batch_processor.py +189 -0
  7. tusk_drift_python_sdk-0.1.0/drift/core/communication/__init__.py +52 -0
  8. tusk_drift_python_sdk-0.1.0/drift/core/communication/communicator.py +737 -0
  9. tusk_drift_python_sdk-0.1.0/drift/core/communication/types.py +369 -0
  10. tusk_drift_python_sdk-0.1.0/drift/core/config.py +293 -0
  11. tusk_drift_python_sdk-0.1.0/drift/core/data_normalization.py +220 -0
  12. tusk_drift_python_sdk-0.1.0/drift/core/drift_sdk.py +587 -0
  13. tusk_drift_python_sdk-0.1.0/drift/core/json_schema_helper.py +236 -0
  14. tusk_drift_python_sdk-0.1.0/drift/core/sampling.py +51 -0
  15. tusk_drift_python_sdk-0.1.0/drift/core/span_serialization.py +95 -0
  16. tusk_drift_python_sdk-0.1.0/drift/core/trace_blocking_manager.py +210 -0
  17. tusk_drift_python_sdk-0.1.0/drift/core/tracing/__init__.py +5 -0
  18. tusk_drift_python_sdk-0.1.0/drift/core/tracing/span_exporter.py +144 -0
  19. tusk_drift_python_sdk-0.1.0/drift/core/types.py +228 -0
  20. tusk_drift_python_sdk-0.1.0/drift/instrumentation/__init__.py +6 -0
  21. tusk_drift_python_sdk-0.1.0/drift/instrumentation/base.py +39 -0
  22. tusk_drift_python_sdk-0.1.0/drift/instrumentation/fastapi/__init__.py +3 -0
  23. tusk_drift_python_sdk-0.1.0/drift/instrumentation/fastapi/instrumentation.py +613 -0
  24. tusk_drift_python_sdk-0.1.0/drift/instrumentation/flask/__init__.py +3 -0
  25. tusk_drift_python_sdk-0.1.0/drift/instrumentation/flask/instrumentation.py +423 -0
  26. tusk_drift_python_sdk-0.1.0/drift/instrumentation/http/__init__.py +5 -0
  27. tusk_drift_python_sdk-0.1.0/drift/instrumentation/http/transform_engine.py +748 -0
  28. tusk_drift_python_sdk-0.1.0/drift/instrumentation/registry.py +106 -0
  29. tusk_drift_python_sdk-0.1.0/drift/instrumentation/requests/__init__.py +5 -0
  30. tusk_drift_python_sdk-0.1.0/drift/instrumentation/requests/instrumentation.py +698 -0
  31. tusk_drift_python_sdk-0.1.0/drift/tracing/__init__.py +1 -0
  32. tusk_drift_python_sdk-0.1.0/drift/tracing/adapters/__init__.py +28 -0
  33. tusk_drift_python_sdk-0.1.0/drift/tracing/adapters/api.py +250 -0
  34. tusk_drift_python_sdk-0.1.0/drift/tracing/adapters/base.py +77 -0
  35. tusk_drift_python_sdk-0.1.0/drift/tracing/adapters/filesystem.py +200 -0
  36. tusk_drift_python_sdk-0.1.0/drift/tracing/adapters/memory.py +157 -0
  37. tusk_drift_python_sdk-0.1.0/drift/version.py +10 -0
  38. tusk_drift_python_sdk-0.1.0/py.typed +0 -0
  39. tusk_drift_python_sdk-0.1.0/pyproject.toml +55 -0
  40. tusk_drift_python_sdk-0.1.0/setup.cfg +4 -0
  41. tusk_drift_python_sdk-0.1.0/tests/test_transform_engine.py +138 -0
  42. tusk_drift_python_sdk-0.1.0/tusk_drift_python_sdk.egg-info/PKG-INFO +83 -0
  43. tusk_drift_python_sdk-0.1.0/tusk_drift_python_sdk.egg-info/SOURCES.txt +44 -0
  44. tusk_drift_python_sdk-0.1.0/tusk_drift_python_sdk.egg-info/dependency_links.txt +1 -0
  45. tusk_drift_python_sdk-0.1.0/tusk_drift_python_sdk.egg-info/requires.txt +20 -0
  46. tusk_drift_python_sdk-0.1.0/tusk_drift_python_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ include py.typed
4
+ recursive-include drift *.py
5
+ recursive-include drift *.typed
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: tusk-drift-python-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Tusk Drift instrumentation and replay
5
+ Author-email: Tusk <support@usetusk.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://usetusk.ai
8
+ Project-URL: Documentation, https://docs.usetusk.ai
9
+ Project-URL: Repository, https://github.com/Use-Tusk/drift-node-sdk
10
+ Project-URL: Issues, https://github.com/Use-Tusk/drift-node-sdk/issues
11
+ Keywords: tusk,drift,testing,instrumentation,tracing,replay
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: protobuf>=6.0
22
+ Requires-Dist: PyYAML>=6.0
23
+ Requires-Dist: requests>=2.32.5
24
+ Requires-Dist: tusk-drift-schemas>=0.1.9
25
+ Requires-Dist: aiohttp>=3.9.0
26
+ Requires-Dist: aiofiles>=23.0.0
27
+ Provides-Extra: flask
28
+ Requires-Dist: Flask>=3.1.2; extra == "flask"
29
+ Provides-Extra: fastapi
30
+ Requires-Dist: fastapi>=0.115.6; extra == "fastapi"
31
+ Requires-Dist: uvicorn>=0.34.2; extra == "fastapi"
32
+ Requires-Dist: starlette<0.42.0; extra == "fastapi"
33
+ Provides-Extra: dev
34
+ Requires-Dist: Flask>=3.1.2; extra == "dev"
35
+ Requires-Dist: fastapi>=0.115.6; extra == "dev"
36
+ Requires-Dist: uvicorn>=0.34.2; extra == "dev"
37
+ Requires-Dist: python-jsonpath>=0.10; extra == "dev"
38
+
39
+ # Drift Python SDK
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ python -m venv venv
45
+ . venv/bin/activate
46
+ pip install -r requirements.txt
47
+ ```
48
+
49
+ Alternatively, if you have [direnv](https://direnv.net/), just allow this folder and run `pip install -r requirements.txt`.
50
+
51
+ ## Running Tests
52
+
53
+ ```bash
54
+ # Run all unit tests
55
+ python -m unittest discover -s tests/unit -v
56
+
57
+ # Run all integration tests (Flask/FastAPI)
58
+ timeout 30 python -m unittest discover -s tests/integration -v
59
+
60
+ # Run specific test file
61
+ python -m unittest tests.unit.test_json_schema_helper -v
62
+ python -m unittest tests.unit.test_adapters -v
63
+ ```
64
+
65
+ ### Database Integration Tests
66
+
67
+ ```bash
68
+ # Start test databases
69
+ docker compose -f docker-compose.test.yml up -d
70
+
71
+ # Run database tests
72
+ python -m unittest tests.integration.test_database -v
73
+
74
+ # Stop databases
75
+ docker compose -f docker-compose.test.yml down
76
+ ```
77
+
78
+ ### Demo Scripts
79
+
80
+ ```bash
81
+ timeout 10 python tests/test_flask_demo.py
82
+ timeout 10 python tests/test_fastapi_demo.py
83
+ ```
@@ -0,0 +1,45 @@
1
+ # Drift Python SDK
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ python -m venv venv
7
+ . venv/bin/activate
8
+ pip install -r requirements.txt
9
+ ```
10
+
11
+ Alternatively, if you have [direnv](https://direnv.net/), just allow this folder and run `pip install -r requirements.txt`.
12
+
13
+ ## Running Tests
14
+
15
+ ```bash
16
+ # Run all unit tests
17
+ python -m unittest discover -s tests/unit -v
18
+
19
+ # Run all integration tests (Flask/FastAPI)
20
+ timeout 30 python -m unittest discover -s tests/integration -v
21
+
22
+ # Run specific test file
23
+ python -m unittest tests.unit.test_json_schema_helper -v
24
+ python -m unittest tests.unit.test_adapters -v
25
+ ```
26
+
27
+ ### Database Integration Tests
28
+
29
+ ```bash
30
+ # Start test databases
31
+ docker compose -f docker-compose.test.yml up -d
32
+
33
+ # Run database tests
34
+ python -m unittest tests.integration.test_database -v
35
+
36
+ # Stop databases
37
+ docker compose -f docker-compose.test.yml down
38
+ ```
39
+
40
+ ### Demo Scripts
41
+
42
+ ```bash
43
+ timeout 10 python tests/test_flask_demo.py
44
+ timeout 10 python tests/test_fastapi_demo.py
45
+ ```
@@ -0,0 +1,68 @@
1
+ """Drift Python SDK for distributed tracing and instrumentation."""
2
+
3
+ from .core import (
4
+ TuskDrift,
5
+ CleanSpanData,
6
+ PackageType,
7
+ SpanKind,
8
+ StatusCode,
9
+ DriftMode,
10
+ BatchSpanProcessorConfig,
11
+ # Config
12
+ TuskConfig,
13
+ TuskFileConfig,
14
+ ServiceConfig,
15
+ RecordingConfig,
16
+ TracesConfig,
17
+ TuskApiConfig,
18
+ load_tusk_config,
19
+ find_project_root,
20
+ )
21
+ from .instrumentation.flask import FlaskInstrumentation
22
+ from .instrumentation.fastapi import FastAPIInstrumentation
23
+ from .instrumentation.requests import RequestsInstrumentation
24
+ from .tracing.adapters import (
25
+ SpanExportAdapter,
26
+ ExportResult,
27
+ ExportResultCode,
28
+ InMemorySpanAdapter,
29
+ FilesystemSpanAdapter,
30
+ ApiSpanAdapter,
31
+ ApiSpanAdapterConfig,
32
+ create_api_adapter,
33
+ )
34
+
35
+ __version__ = "0.1.0"
36
+
37
+ __all__ = [
38
+ # Core
39
+ "TuskDrift",
40
+ "CleanSpanData",
41
+ "PackageType",
42
+ "SpanKind",
43
+ "StatusCode",
44
+ "DriftMode",
45
+ "BatchSpanProcessorConfig",
46
+ # Config
47
+ "TuskConfig",
48
+ "TuskFileConfig",
49
+ "ServiceConfig",
50
+ "RecordingConfig",
51
+ "TracesConfig",
52
+ "TuskApiConfig",
53
+ "load_tusk_config",
54
+ "find_project_root",
55
+ # Instrumentations
56
+ "FlaskInstrumentation",
57
+ "FastAPIInstrumentation",
58
+ "RequestsInstrumentation",
59
+ # Adapters
60
+ "SpanExportAdapter",
61
+ "ExportResult",
62
+ "ExportResultCode",
63
+ "InMemorySpanAdapter",
64
+ "FilesystemSpanAdapter",
65
+ "ApiSpanAdapter",
66
+ "ApiSpanAdapterConfig",
67
+ "create_api_adapter",
68
+ ]
@@ -0,0 +1,66 @@
1
+ """Core module for the Drift SDK."""
2
+
3
+ from .drift_sdk import TuskDrift
4
+ from .types import DriftMode, CleanSpanData, PackageType, SpanKind, StatusCode
5
+ from .config import (
6
+ TuskConfig,
7
+ TuskFileConfig,
8
+ ServiceConfig,
9
+ RecordingConfig,
10
+ TracesConfig,
11
+ TuskApiConfig,
12
+ load_tusk_config,
13
+ find_project_root,
14
+ )
15
+ from .batch_processor import BatchSpanProcessor, BatchSpanProcessorConfig
16
+ from .sampling import should_sample, validate_sampling_rate
17
+ from .data_normalization import (
18
+ normalize_input_data,
19
+ remove_none_values,
20
+ create_span_input_value,
21
+ create_mock_input_value,
22
+ )
23
+ from .trace_blocking_manager import (
24
+ TraceBlockingManager,
25
+ estimate_span_size,
26
+ should_block_span,
27
+ MAX_SPAN_SIZE_MB,
28
+ MAX_SPAN_SIZE_BYTES,
29
+ )
30
+
31
+ __all__ = [
32
+ # Main SDK
33
+ "TuskDrift",
34
+ # Config
35
+ "TuskConfig",
36
+ "TuskFileConfig",
37
+ "ServiceConfig",
38
+ "RecordingConfig",
39
+ "TracesConfig",
40
+ "TuskApiConfig",
41
+ "load_tusk_config",
42
+ "find_project_root",
43
+ # Types
44
+ "DriftMode",
45
+ "CleanSpanData",
46
+ "PackageType",
47
+ "SpanKind",
48
+ "StatusCode",
49
+ # Batching
50
+ "BatchSpanProcessor",
51
+ "BatchSpanProcessorConfig",
52
+ # Sampling
53
+ "should_sample",
54
+ "validate_sampling_rate",
55
+ # Data normalization
56
+ "normalize_input_data",
57
+ "remove_none_values",
58
+ "create_span_input_value",
59
+ "create_mock_input_value",
60
+ # Trace blocking
61
+ "TraceBlockingManager",
62
+ "estimate_span_size",
63
+ "should_block_span",
64
+ "MAX_SPAN_SIZE_MB",
65
+ "MAX_SPAN_SIZE_BYTES",
66
+ ]
@@ -0,0 +1,189 @@
1
+ """Batch span processor for efficient span export."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import threading
8
+ import time
9
+ from collections import deque
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from .types import CleanSpanData
15
+ from ..tracing.adapters.base import SpanExportAdapter
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class BatchSpanProcessorConfig:
22
+ """Configuration for the batch span processor."""
23
+
24
+ # Maximum queue size before spans are dropped
25
+ max_queue_size: int = 2048
26
+ # Maximum batch size per export
27
+ max_export_batch_size: int = 512
28
+ # Interval between scheduled exports (in seconds)
29
+ scheduled_delay_seconds: float = 2.0
30
+ # Maximum time to wait for export (in seconds)
31
+ export_timeout_seconds: float = 30.0
32
+
33
+
34
+ class BatchSpanProcessor:
35
+ """
36
+ Batches spans and exports them periodically or when batch size is reached.
37
+
38
+ Matches the behavior of OpenTelemetry's BatchSpanProcessor:
39
+ - Queues spans in memory
40
+ - Exports in batches at regular intervals or when max batch size is reached
41
+ - Drops spans if queue is full
42
+ - Handles graceful shutdown with final export
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ adapters: list["SpanExportAdapter"],
48
+ config: BatchSpanProcessorConfig | None = None,
49
+ ) -> None:
50
+ """
51
+ Initialize the batch processor.
52
+
53
+ Args:
54
+ adapters: List of adapters to export spans to
55
+ config: Optional configuration (uses defaults if not provided)
56
+ """
57
+ self._adapters = adapters
58
+ self._config = config or BatchSpanProcessorConfig()
59
+ self._queue: deque[CleanSpanData] = deque(maxlen=self._config.max_queue_size)
60
+ self._lock = threading.Lock()
61
+ self._shutdown_event = threading.Event()
62
+ self._export_thread: threading.Thread | None = None
63
+ self._started = False
64
+ self._dropped_spans = 0
65
+
66
+ def start(self) -> None:
67
+ """Start the background export thread."""
68
+ if self._started:
69
+ return
70
+
71
+ self._started = True
72
+ self._shutdown_event.clear()
73
+ self._export_thread = threading.Thread(
74
+ target=self._export_loop,
75
+ daemon=True,
76
+ name="drift-batch-exporter",
77
+ )
78
+ self._export_thread.start()
79
+ logger.debug("BatchSpanProcessor started")
80
+
81
+ def stop(self, timeout: float | None = None) -> None:
82
+ """
83
+ Stop the processor and export remaining spans.
84
+
85
+ Args:
86
+ timeout: Maximum time to wait for final export
87
+ """
88
+ if not self._started:
89
+ return
90
+
91
+ self._shutdown_event.set()
92
+
93
+ if self._export_thread is not None:
94
+ self._export_thread.join(timeout=timeout or self._config.export_timeout_seconds)
95
+
96
+ # Final export of remaining spans
97
+ self._force_flush()
98
+
99
+ self._started = False
100
+ logger.debug(f"BatchSpanProcessor stopped. Dropped {self._dropped_spans} spans total.")
101
+
102
+ def add_span(self, span: "CleanSpanData") -> bool:
103
+ """
104
+ Add a span to the queue for export.
105
+
106
+ Args:
107
+ span: The span to add
108
+
109
+ Returns:
110
+ True if span was added, False if queue is full and span was dropped
111
+ """
112
+ with self._lock:
113
+ if len(self._queue) >= self._config.max_queue_size:
114
+ self._dropped_spans += 1
115
+ logger.warning(
116
+ f"Span queue full ({self._config.max_queue_size}), dropping span. "
117
+ f"Total dropped: {self._dropped_spans}"
118
+ )
119
+ return False
120
+
121
+ self._queue.append(span)
122
+
123
+ # Trigger immediate export if batch size reached
124
+ if len(self._queue) >= self._config.max_export_batch_size:
125
+ # Signal export thread to wake up (if using condition variable)
126
+ pass
127
+
128
+ return True
129
+
130
+ def _export_loop(self) -> None:
131
+ """Background thread that periodically exports spans."""
132
+ while not self._shutdown_event.is_set():
133
+ # Wait for scheduled delay or shutdown
134
+ self._shutdown_event.wait(timeout=self._config.scheduled_delay_seconds)
135
+
136
+ if self._shutdown_event.is_set():
137
+ break
138
+
139
+ self._export_batch()
140
+
141
+ def _export_batch(self) -> None:
142
+ """Export a batch of spans from the queue."""
143
+ # Get batch of spans
144
+ batch: list[CleanSpanData] = []
145
+ with self._lock:
146
+ while self._queue and len(batch) < self._config.max_export_batch_size:
147
+ batch.append(self._queue.popleft())
148
+
149
+ if not batch:
150
+ return
151
+
152
+ # Export to all adapters
153
+ for adapter in self._adapters:
154
+ try:
155
+ # Handle async adapters
156
+ if asyncio.iscoroutinefunction(adapter.export_spans):
157
+ try:
158
+ loop = asyncio.get_running_loop()
159
+ asyncio.create_task(adapter.export_spans(batch))
160
+ except RuntimeError:
161
+ # No running loop, create one
162
+ asyncio.run(adapter.export_spans(batch))
163
+ else:
164
+ adapter.export_spans(batch) # type: ignore
165
+
166
+ logger.debug(f"Exported {len(batch)} spans via {adapter.name}")
167
+
168
+ except Exception as e:
169
+ logger.error(f"Failed to export batch via {adapter.name}: {e}")
170
+
171
+ def _force_flush(self) -> None:
172
+ """Force export all remaining spans in the queue."""
173
+ while True:
174
+ with self._lock:
175
+ if not self._queue:
176
+ break
177
+
178
+ self._export_batch()
179
+
180
+ @property
181
+ def queue_size(self) -> int:
182
+ """Get the current queue size."""
183
+ with self._lock:
184
+ return len(self._queue)
185
+
186
+ @property
187
+ def dropped_span_count(self) -> int:
188
+ """Get the number of dropped spans."""
189
+ return self._dropped_spans
@@ -0,0 +1,52 @@
1
+ """CLI communication module for Drift SDK.
2
+
3
+ This module handles bidirectional communication between the SDK and the Tusk CLI
4
+ for replay testing. Communication uses Protocol Buffers over Unix sockets or TCP.
5
+ """
6
+
7
+ from .types import (
8
+ MessageType,
9
+ SDKMessageType,
10
+ CLIMessageType,
11
+ ConnectRequest,
12
+ ConnectResponse,
13
+ GetMockRequest,
14
+ GetMockResponse,
15
+ EnvVarRequest,
16
+ EnvVarResponse,
17
+ MockRequestInput,
18
+ MockResponseOutput,
19
+ # Protobuf types (re-exported)
20
+ SdkMessage,
21
+ CliMessage,
22
+ span_to_proto,
23
+ dict_to_span,
24
+ extract_response_data,
25
+ )
26
+ from .communicator import ProtobufCommunicator, CommunicatorConfig
27
+
28
+ __all__ = [
29
+ # Message types
30
+ "MessageType",
31
+ "SDKMessageType",
32
+ "CLIMessageType",
33
+ # Request/Response types
34
+ "ConnectRequest",
35
+ "ConnectResponse",
36
+ "GetMockRequest",
37
+ "GetMockResponse",
38
+ "EnvVarRequest",
39
+ "EnvVarResponse",
40
+ "MockRequestInput",
41
+ "MockResponseOutput",
42
+ # Protobuf types
43
+ "SdkMessage",
44
+ "CliMessage",
45
+ # Utilities
46
+ "span_to_proto",
47
+ "dict_to_span",
48
+ "extract_response_data",
49
+ # Communicator
50
+ "ProtobufCommunicator",
51
+ "CommunicatorConfig",
52
+ ]