scale-gp-beta 0.1.0a21__py3-none-any.whl → 0.1.0a23__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.
- scale_gp_beta/_version.py +1 -1
- scale_gp_beta/lib/tracing/span.py +79 -16
- scale_gp_beta/lib/tracing/trace.py +81 -41
- scale_gp_beta/lib/tracing/tracing.py +1 -1
- {scale_gp_beta-0.1.0a21.dist-info → scale_gp_beta-0.1.0a23.dist-info}/METADATA +241 -3
- {scale_gp_beta-0.1.0a21.dist-info → scale_gp_beta-0.1.0a23.dist-info}/RECORD +8 -8
- {scale_gp_beta-0.1.0a21.dist-info → scale_gp_beta-0.1.0a23.dist-info}/WHEEL +0 -0
- {scale_gp_beta-0.1.0a21.dist-info → scale_gp_beta-0.1.0a23.dist-info}/licenses/LICENSE +0 -0
scale_gp_beta/_version.py
CHANGED
|
@@ -59,18 +59,18 @@ class BaseSpan:
|
|
|
59
59
|
metadata: Optional[SpanMetadataParam] = None,
|
|
60
60
|
span_type: SpanTypeLiterals = "STANDALONE"
|
|
61
61
|
):
|
|
62
|
-
self.
|
|
63
|
-
self.
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
62
|
+
self._name = name
|
|
63
|
+
self._trace_id: str = trace_id or "no_trace_id"
|
|
64
|
+
self._group_id = group_id
|
|
65
|
+
self._span_id: str = span_id or generate_span_id()
|
|
66
|
+
self._parent_span_id = parent_span_id
|
|
67
67
|
self.start_time: Optional[str] = None
|
|
68
68
|
self.end_time: Optional[str] = None
|
|
69
|
-
self.
|
|
70
|
-
self.
|
|
71
|
-
self.
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
69
|
+
self._input: SpanInputParam = input or {}
|
|
70
|
+
self._output: SpanOutputParam = output or {}
|
|
71
|
+
self._metadata: SpanMetadataParam = metadata or {}
|
|
72
|
+
self._span_type: SpanTypeLiterals = span_type
|
|
73
|
+
self._status: SpanStatusLiterals = "SUCCESS"
|
|
74
74
|
self._queue_manager = queue_manager
|
|
75
75
|
|
|
76
76
|
self._contextvar_token: Optional[contextvars.Token[Optional[BaseSpan]]] = None
|
|
@@ -84,6 +84,73 @@ class BaseSpan:
|
|
|
84
84
|
def flush(self, blocking: bool = True) -> None:
|
|
85
85
|
pass
|
|
86
86
|
|
|
87
|
+
@property
|
|
88
|
+
def name(self) -> str:
|
|
89
|
+
return self._name
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def trace_id(self) -> str:
|
|
93
|
+
return self._trace_id
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def group_id(self) -> Optional[str]:
|
|
97
|
+
return self._group_id
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def span_id(self) -> str:
|
|
101
|
+
return self._span_id
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def parent_span_id(self) -> Optional[str]:
|
|
105
|
+
return self._parent_span_id
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def status(self) -> SpanStatusLiterals:
|
|
109
|
+
return self._status
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def span_type(self) -> SpanTypeLiterals:
|
|
113
|
+
return self._span_type
|
|
114
|
+
|
|
115
|
+
# with setters
|
|
116
|
+
@property
|
|
117
|
+
def metadata(self) -> SpanMetadataParam:
|
|
118
|
+
return self._metadata
|
|
119
|
+
|
|
120
|
+
@metadata.setter
|
|
121
|
+
def metadata(self, value: SpanMetadataParam) -> None:
|
|
122
|
+
self._metadata = value
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def input(self) -> SpanInputParam:
|
|
126
|
+
return self._input
|
|
127
|
+
|
|
128
|
+
@input.setter
|
|
129
|
+
def input(self, value: SpanInputParam) -> None:
|
|
130
|
+
self._input = value
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def output(self) -> SpanOutputParam:
|
|
134
|
+
return self._output
|
|
135
|
+
|
|
136
|
+
@output.setter
|
|
137
|
+
def output(self, value: SpanOutputParam) -> None:
|
|
138
|
+
self._output = value
|
|
139
|
+
|
|
140
|
+
def set_error(
|
|
141
|
+
self,
|
|
142
|
+
error_type: Optional[str] = None,
|
|
143
|
+
error_message: Optional[str] = None,
|
|
144
|
+
exception: Optional[BaseException] = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
# Naively record details in metadata for now, note that error capture only supported in context manager
|
|
147
|
+
exception_type = type(exception).__name__ if exception else None
|
|
148
|
+
exception_message = str(exception) if exception else None
|
|
149
|
+
self._status = "ERROR"
|
|
150
|
+
self.metadata["error"] = True
|
|
151
|
+
self.metadata["error_type"] = error_type or exception_type
|
|
152
|
+
self.metadata["error_message"] = error_message or exception_message
|
|
153
|
+
|
|
87
154
|
def __enter__(self) -> BaseSpan:
|
|
88
155
|
self.start()
|
|
89
156
|
return self
|
|
@@ -94,13 +161,9 @@ class BaseSpan:
|
|
|
94
161
|
exc_val: Optional[BaseException],
|
|
95
162
|
exc_tb: Optional[TracebackType]
|
|
96
163
|
) -> None:
|
|
97
|
-
# Naively record details in metadata for now, note that error capture only supported in context manager
|
|
98
164
|
# TODO: support error observations when using direct span.start() and span.end()
|
|
99
165
|
if exc_type is not None:
|
|
100
|
-
self.
|
|
101
|
-
self.metadata["error.type"] = exc_type.__name__
|
|
102
|
-
self.metadata["error.message"] = str(exc_val)
|
|
103
|
-
self.status = "ERROR"
|
|
166
|
+
self.set_error(exception=exc_val)
|
|
104
167
|
self.end()
|
|
105
168
|
|
|
106
169
|
def to_request_params(self) -> SpanCreateRequest:
|
|
@@ -203,7 +266,7 @@ class Span(BaseSpan):
|
|
|
203
266
|
):
|
|
204
267
|
super().__init__(name, trace_id, queue_manager, span_id, parent_span_id, group_id, input, output, metadata, span_type)
|
|
205
268
|
self._queue_manager: TraceQueueManager = queue_manager
|
|
206
|
-
self.
|
|
269
|
+
self._trace_id: str = trace_id
|
|
207
270
|
|
|
208
271
|
@override
|
|
209
272
|
def flush(self, blocking: bool = True) -> None:
|
|
@@ -4,23 +4,30 @@ from types import TracebackType
|
|
|
4
4
|
from typing import Type, Optional
|
|
5
5
|
from typing_extensions import override
|
|
6
6
|
|
|
7
|
-
from .span import Span, NoOpSpan
|
|
7
|
+
from .span import Span, BaseSpan, NoOpSpan
|
|
8
8
|
from .util import generate_trace_id
|
|
9
9
|
from .scope import Scope
|
|
10
|
-
from .types import SpanInputParam, SpanOutputParam, SpanTypeLiterals, SpanMetadataParam
|
|
10
|
+
from .types import SpanInputParam, SpanOutputParam, SpanTypeLiterals, SpanMetadataParam, SpanStatusLiterals
|
|
11
11
|
from .trace_queue_manager import TraceQueueManager
|
|
12
12
|
|
|
13
13
|
log: logging.Logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class BaseTrace:
|
|
17
|
-
def __init__(
|
|
18
|
-
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
queue_manager: Optional[TraceQueueManager],
|
|
20
|
+
root_span: BaseSpan,
|
|
21
|
+
trace_id: str
|
|
22
|
+
) -> None:
|
|
23
|
+
self._trace_id = trace_id
|
|
19
24
|
self.queue_manager = queue_manager
|
|
20
25
|
|
|
21
26
|
self._in_progress = False
|
|
22
27
|
self._contextvar_token: Optional[contextvars.Token[Optional[BaseTrace]]] = None
|
|
23
28
|
|
|
29
|
+
self.root_span = root_span
|
|
30
|
+
|
|
24
31
|
def start(self) -> None:
|
|
25
32
|
pass
|
|
26
33
|
|
|
@@ -30,8 +37,62 @@ class BaseTrace:
|
|
|
30
37
|
def flush(self, blocking: bool = True) -> None:
|
|
31
38
|
pass
|
|
32
39
|
|
|
40
|
+
@property
|
|
41
|
+
def metadata(self) -> SpanMetadataParam:
|
|
42
|
+
return self.root_span.metadata
|
|
43
|
+
|
|
44
|
+
@metadata.setter
|
|
45
|
+
def metadata(self, value: SpanMetadataParam) -> None:
|
|
46
|
+
self.root_span.metadata = value
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def input(self) -> SpanInputParam:
|
|
50
|
+
return self.root_span.input
|
|
51
|
+
|
|
52
|
+
@input.setter
|
|
53
|
+
def input(self, value: SpanInputParam) -> None:
|
|
54
|
+
self.root_span.input = value
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def output(self) -> SpanOutputParam:
|
|
58
|
+
return self.root_span.output
|
|
59
|
+
|
|
60
|
+
@output.setter
|
|
61
|
+
def output(self, value: SpanOutputParam) -> None:
|
|
62
|
+
self.root_span.output = value
|
|
63
|
+
|
|
64
|
+
# no setters
|
|
65
|
+
@property
|
|
66
|
+
def name(self) -> Optional[str]:
|
|
67
|
+
return self.root_span.name
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def span_id(self) -> Optional[str]:
|
|
71
|
+
return self.root_span.span_id
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def trace_id(self) -> Optional[str]:
|
|
75
|
+
return self._trace_id
|
|
76
|
+
|
|
77
|
+
@property
|
|
33
78
|
def group_id(self) -> Optional[str]:
|
|
34
|
-
return
|
|
79
|
+
return self.root_span.group_id
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def span_type(self) -> SpanTypeLiterals:
|
|
83
|
+
return self.root_span.span_type
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def status(self) -> SpanStatusLiterals:
|
|
87
|
+
return self.root_span.status
|
|
88
|
+
|
|
89
|
+
def set_error(
|
|
90
|
+
self,
|
|
91
|
+
error_type: Optional[str] = None,
|
|
92
|
+
error_message: Optional[str] = None,
|
|
93
|
+
exception: Optional[BaseException] = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
self.root_span.set_error(error_type=error_type, error_message=error_message, exception=exception)
|
|
35
96
|
|
|
36
97
|
def __enter__(self) -> "BaseTrace":
|
|
37
98
|
self.start()
|
|
@@ -47,7 +108,12 @@ class BaseTrace:
|
|
|
47
108
|
|
|
48
109
|
@override
|
|
49
110
|
def __repr__(self) -> str:
|
|
50
|
-
return
|
|
111
|
+
return (
|
|
112
|
+
f"{self.__class__.__name__}("
|
|
113
|
+
f"trace_id='{self.trace_id}', "
|
|
114
|
+
f"root_span='{repr(self.root_span)}', "
|
|
115
|
+
")"
|
|
116
|
+
)
|
|
51
117
|
|
|
52
118
|
@override
|
|
53
119
|
def __str__(self) -> str:
|
|
@@ -67,12 +133,11 @@ class NoOpTrace(BaseTrace):
|
|
|
67
133
|
output: Optional[SpanOutputParam] = None,
|
|
68
134
|
metadata: Optional[SpanMetadataParam] = None,
|
|
69
135
|
):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.root_span = NoOpSpan(
|
|
136
|
+
trace_id = trace_id or generate_trace_id()
|
|
137
|
+
root_span = NoOpSpan(
|
|
73
138
|
name=name,
|
|
74
139
|
span_id=span_id,
|
|
75
|
-
trace_id=
|
|
140
|
+
trace_id=trace_id,
|
|
76
141
|
group_id=group_id,
|
|
77
142
|
queue_manager=queue_manager,
|
|
78
143
|
metadata=metadata,
|
|
@@ -80,15 +145,7 @@ class NoOpTrace(BaseTrace):
|
|
|
80
145
|
input=input,
|
|
81
146
|
output=output,
|
|
82
147
|
)
|
|
83
|
-
|
|
84
|
-
@override
|
|
85
|
-
def __repr__(self) -> str:
|
|
86
|
-
return (
|
|
87
|
-
f"{self.__class__.__name__}("
|
|
88
|
-
f"trace_id='{self.trace_id}', "
|
|
89
|
-
f"root_span='{repr(self.root_span)}', "
|
|
90
|
-
")"
|
|
91
|
-
)
|
|
148
|
+
super().__init__(queue_manager, root_span, trace_id)
|
|
92
149
|
|
|
93
150
|
@override
|
|
94
151
|
def start(self) -> None:
|
|
@@ -98,10 +155,6 @@ class NoOpTrace(BaseTrace):
|
|
|
98
155
|
def end(self) -> None:
|
|
99
156
|
self.root_span.end()
|
|
100
157
|
|
|
101
|
-
@override
|
|
102
|
-
def group_id(self) -> Optional[str]:
|
|
103
|
-
return self.root_span.group_id
|
|
104
|
-
|
|
105
158
|
|
|
106
159
|
class Trace(BaseTrace):
|
|
107
160
|
def __init__(
|
|
@@ -116,13 +169,11 @@ class Trace(BaseTrace):
|
|
|
116
169
|
output: Optional[SpanOutputParam] = None,
|
|
117
170
|
metadata: Optional[SpanMetadataParam] = None,
|
|
118
171
|
):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
self.root_span = Span(
|
|
172
|
+
trace_id = trace_id or generate_trace_id()
|
|
173
|
+
root_span = Span(
|
|
123
174
|
name=name,
|
|
124
175
|
span_id=span_id,
|
|
125
|
-
trace_id=
|
|
176
|
+
trace_id=trace_id,
|
|
126
177
|
group_id=group_id,
|
|
127
178
|
queue_manager=queue_manager,
|
|
128
179
|
metadata=metadata,
|
|
@@ -130,6 +181,8 @@ class Trace(BaseTrace):
|
|
|
130
181
|
input=input,
|
|
131
182
|
output=output,
|
|
132
183
|
)
|
|
184
|
+
super().__init__(queue_manager, root_span, trace_id)
|
|
185
|
+
self.queue_manager: TraceQueueManager = queue_manager
|
|
133
186
|
|
|
134
187
|
@override
|
|
135
188
|
def start(self) -> None:
|
|
@@ -162,16 +215,3 @@ class Trace(BaseTrace):
|
|
|
162
215
|
@override
|
|
163
216
|
def flush(self, blocking: bool = True) -> None:
|
|
164
217
|
self.root_span.flush(blocking=blocking)
|
|
165
|
-
|
|
166
|
-
@override
|
|
167
|
-
def group_id(self) -> Optional[str]:
|
|
168
|
-
return self.root_span.group_id
|
|
169
|
-
|
|
170
|
-
@override
|
|
171
|
-
def __repr__(self) -> str:
|
|
172
|
-
return (
|
|
173
|
-
f"{self.__class__.__name__}("
|
|
174
|
-
f"trace_id='{self.trace_id}', "
|
|
175
|
-
f"root_span='{repr(self.root_span)}', "
|
|
176
|
-
")"
|
|
177
|
-
)
|
|
@@ -182,7 +182,7 @@ def create_span(
|
|
|
182
182
|
|
|
183
183
|
scoped_trace = current_trace()
|
|
184
184
|
scoped_trace_id = scoped_trace.trace_id if scoped_trace else None
|
|
185
|
-
scoped_group_id = scoped_trace.group_id
|
|
185
|
+
scoped_group_id = scoped_trace.group_id if scoped_trace else None
|
|
186
186
|
scoped_span = current_span()
|
|
187
187
|
scoped_span_id = scoped_span.span_id if scoped_span else None
|
|
188
188
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scale-gp-beta
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a23
|
|
4
4
|
Summary: The official Python library for the Scale GP API
|
|
5
5
|
Project-URL: Homepage, https://github.com/scaleapi/sgp-python-beta
|
|
6
6
|
Project-URL: Repository, https://github.com/scaleapi/sgp-python-beta
|
|
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
|
|
32
32
|
# Scale GP Python API library
|
|
33
33
|
|
|
34
|
-
[](https://pypi.org/project/scale-gp-beta/)
|
|
34
|
+
[>)](https://pypi.org/project/scale-gp-beta/)
|
|
35
35
|
|
|
36
36
|
The Scale GP Python library provides convenient access to the Scale GP REST API from any Python 3.8+
|
|
37
37
|
application. The library includes type definitions for all request params and response fields,
|
|
@@ -77,6 +77,244 @@ we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
|
|
|
77
77
|
to add `SGP_API_KEY="My API Key"` to your `.env` file
|
|
78
78
|
so that your API Key is not stored in source control.
|
|
79
79
|
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Tracing & Spans
|
|
83
|
+
|
|
84
|
+
The SGP Tracing library provides a convenient way to instrument your Python applications with tracing capabilities, allowing you to generate, manage, and send spans to the Scale GP platform. This enables detailed monitoring and debugging of your workflows.
|
|
85
|
+
|
|
86
|
+
### Quick Start Examples
|
|
87
|
+
|
|
88
|
+
For runnable examples, see the [examples/tracing](https://github.com/scaleapi/sgp-python-beta/tree/main/examples/tracing) directory in the repository.
|
|
89
|
+
|
|
90
|
+
### Using the SDK
|
|
91
|
+
|
|
92
|
+
#### Initialization
|
|
93
|
+
|
|
94
|
+
Before you can create any traces or spans, you should initialize the tracing SDK with your `SGPClient`. It's best practice to do this once at your application's entry point. You can omit this step if you have set the `SGP_API_KEY` and `SGP_ACCOUNT_ID` environment variables, as the SDK will attempt to create a default client.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
98
|
+
from scale_gp_beta import SGPClient
|
|
99
|
+
|
|
100
|
+
client = SGPClient(api_key="YOUR_API_KEY", account_id="YOUR_ACCOUNT_ID")
|
|
101
|
+
tracing.init(client=client)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Tracing uses the `SGPClient` for all requests. You can edit the `base_url` or any other parameters via the client you pass to `init()`.
|
|
105
|
+
|
|
106
|
+
#### Disabling Tracing
|
|
107
|
+
|
|
108
|
+
You can disable tracing by setting the environment variable `DISABLE_SCALE_TRACING` or programmatically via the `disabled` parameter in `init()`.
|
|
109
|
+
|
|
110
|
+
#### Core Concepts
|
|
111
|
+
|
|
112
|
+
The SDK revolves around two primary concepts: **Traces** and **Spans**.
|
|
113
|
+
|
|
114
|
+
* **Trace:** A trace represents a complete workflow or transaction, such as a web request or an AI agent's operation. It's a collection of related spans. Every trace has a single **root span**.
|
|
115
|
+
* **Span:** A span represents a single unit of work within a trace, like a function call, a database query, or an external API request. Spans can be nested to show hierarchical relationships.
|
|
116
|
+
|
|
117
|
+
When starting a trace, we will also create a root span. Server-side, we do not record the trace resource, only spans, but rely on the root span for trace data.
|
|
118
|
+
|
|
119
|
+
#### Creating Traces and Spans
|
|
120
|
+
|
|
121
|
+
The SDK offers flexible ways to create traces and spans: using **context managers** for automatic start/end handling, or **explicit control** for manual lifecycle management.
|
|
122
|
+
|
|
123
|
+
##### 1\. Using Context Managers (Recommended)
|
|
124
|
+
|
|
125
|
+
The most straightforward way to create traces and spans is by using them as context managers (`with` statements). This ensures that spans are automatically started and ended, and errors are captured.
|
|
126
|
+
|
|
127
|
+
**Creating a Trace with a Root Span:**
|
|
128
|
+
|
|
129
|
+
Use `tracing.create_trace()` as a context manager to define a new trace. This automatically creates a root span for your trace.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
133
|
+
|
|
134
|
+
def my_workflow():
|
|
135
|
+
with tracing.create_trace(name="my_application_workflow", metadata={"env": "production"}):
|
|
136
|
+
# All spans created within this block will belong to "my_application_workflow" trace
|
|
137
|
+
print("Starting my application workflow...")
|
|
138
|
+
# ... your workflow logic
|
|
139
|
+
print("Application workflow completed.")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Creating Spans within a Trace:**
|
|
143
|
+
|
|
144
|
+
Inside a `create_trace` block, use `tracing.create_span()` as a context manager. These spans will automatically be associated with the current trace and parent span (if one exists).
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import time
|
|
148
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
149
|
+
|
|
150
|
+
def fibonacci(curr: int) -> int:
|
|
151
|
+
with tracing.create_span("fibonacci_calculation", input={"curr": curr}) as span:
|
|
152
|
+
time.sleep(0.1) # Simulate some work
|
|
153
|
+
if curr < 2:
|
|
154
|
+
span.output = {"res": curr}
|
|
155
|
+
return curr
|
|
156
|
+
res = fibonacci(curr - 1) + fibonacci(curr - 2)
|
|
157
|
+
span.output = {"res": res}
|
|
158
|
+
return res
|
|
159
|
+
|
|
160
|
+
def main_traced_example():
|
|
161
|
+
with tracing.create_trace("my_fibonacci_trace"): # Creates a root span
|
|
162
|
+
# This span will be a child of the "my_fibonacci_trace" root span
|
|
163
|
+
with tracing.create_span("main_execution", metadata={"version": "1.0"}) as main_span:
|
|
164
|
+
fib_result = fibonacci(5)
|
|
165
|
+
main_span.output = {"final_fib_result": fib_result}
|
|
166
|
+
print(f"Fibonacci(5) = {fib_result}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
##### 2\. Explicit Control
|
|
170
|
+
|
|
171
|
+
For scenarios where context managers aren't suitable, you can manually start and end spans. This approach requires more diligence to ensure all spans are properly ended and maintaining consistency.
|
|
172
|
+
|
|
173
|
+
**Manually Managing Spans (without an explicit Trace context):**
|
|
174
|
+
|
|
175
|
+
You can create spans and explicitly provide their `trace_id` and `parent_id` for fine-grained control. This is useful when integrating with existing systems that manage trace IDs.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import uuid
|
|
179
|
+
import time
|
|
180
|
+
import random
|
|
181
|
+
from typing import Any, Dict
|
|
182
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
183
|
+
|
|
184
|
+
class MockDatabase:
|
|
185
|
+
def __init__(self) -> None:
|
|
186
|
+
self._data = {
|
|
187
|
+
"SELECT * FROM users WHERE id = 1;": {"id": 1, "name": "Alice"},
|
|
188
|
+
"SELECT * FROM users WHERE id = 2;": {"id": 2, "name": "Bob"},
|
|
189
|
+
}
|
|
190
|
+
def execute_query(self, query: str, trace_id: str) -> Dict[str, Any]:
|
|
191
|
+
db_span = tracing.create_span("db_query", input={"query": query}, trace_id=trace_id)
|
|
192
|
+
db_span.start()
|
|
193
|
+
try:
|
|
194
|
+
time.sleep(random.uniform(0.1, 0.3)) # Simulate delay
|
|
195
|
+
result = self._data.get(query, {})
|
|
196
|
+
db_span.output = {"result": result}
|
|
197
|
+
return result
|
|
198
|
+
finally:
|
|
199
|
+
db_span.end()
|
|
200
|
+
|
|
201
|
+
def get_user_from_db_explicit(db: MockDatabase, user_id: int, trace_id: str) -> Dict[str, Any]:
|
|
202
|
+
with tracing.create_span("get_user_from_db", input={"user_id": user_id}, trace_id=trace_id):
|
|
203
|
+
query = f"SELECT * FROM users WHERE id = {user_id};"
|
|
204
|
+
return db.execute_query(query, trace_id)
|
|
205
|
+
|
|
206
|
+
def main_explicit_control_example():
|
|
207
|
+
db = MockDatabase()
|
|
208
|
+
my_trace_id = str(uuid.uuid4())
|
|
209
|
+
# Manually create a root span
|
|
210
|
+
main_span = tracing.create_span("main_explicit_call", metadata={"env": "local"}, trace_id=my_trace_id)
|
|
211
|
+
main_span.start()
|
|
212
|
+
try:
|
|
213
|
+
user = get_user_from_db_explicit(db, 1, my_trace_id)
|
|
214
|
+
print(f"Retrieved user: {user.get('name')}")
|
|
215
|
+
finally:
|
|
216
|
+
main_span.end()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Exporting Existing Tracing Data (Manual Timestamps)**
|
|
220
|
+
|
|
221
|
+
You can even pre-define `start_time`, `end_time`, `span_id`, `parent_id`, and `trace_id` if you need to report historical data or reconstruct traces.
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import uuid
|
|
225
|
+
from datetime import datetime, timezone, timedelta
|
|
226
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
227
|
+
|
|
228
|
+
parent_span_id = str(uuid.uuid4())
|
|
229
|
+
trace_id = str(uuid.uuid4())
|
|
230
|
+
child_span_id = str(uuid.uuid4())
|
|
231
|
+
|
|
232
|
+
now = datetime.now(timezone.utc)
|
|
233
|
+
|
|
234
|
+
# Parent Span
|
|
235
|
+
parent_span = tracing.create_span(
|
|
236
|
+
"my_parent_span_name",
|
|
237
|
+
input={"test": "input"},
|
|
238
|
+
output={"test": "output"},
|
|
239
|
+
metadata={"test": "metadata"},
|
|
240
|
+
span_id=parent_span_id,
|
|
241
|
+
trace_id=trace_id,
|
|
242
|
+
)
|
|
243
|
+
parent_span.start_time = (now - timedelta(minutes=10)).isoformat()
|
|
244
|
+
parent_span.end_time = now.isoformat()
|
|
245
|
+
parent_span.flush(blocking=True)
|
|
246
|
+
|
|
247
|
+
# Child Span
|
|
248
|
+
child_span = tracing.create_span(
|
|
249
|
+
"my_child_span_name",
|
|
250
|
+
input={"test": "another input"},
|
|
251
|
+
output={"test": "another output"},
|
|
252
|
+
metadata={"test": "another metadata"},
|
|
253
|
+
span_id=child_span_id,
|
|
254
|
+
trace_id=trace_id,
|
|
255
|
+
parent_id=parent_span_id,
|
|
256
|
+
)
|
|
257
|
+
child_span.start_time = (now - timedelta(minutes=6)).isoformat()
|
|
258
|
+
child_span.end_time = (now - timedelta(minutes=2)).isoformat()
|
|
259
|
+
child_span.flush()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Note that `span.flush()` will by default block the main thread until the request has finished. Use `blocking=False` to enqueue the request which will be picked up by the background worker.
|
|
263
|
+
|
|
264
|
+
#### Helper Methods
|
|
265
|
+
|
|
266
|
+
You can retrieve the currently active span or trace in the execution context using `current_span()` and `current_trace()`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
270
|
+
|
|
271
|
+
def nested_function():
|
|
272
|
+
with tracing.create_span("nested_operation"):
|
|
273
|
+
current = tracing.current_span()
|
|
274
|
+
if current:
|
|
275
|
+
print(f"Currently active span: {current.name} (ID: {current.span_id})")
|
|
276
|
+
current_t = tracing.current_trace()
|
|
277
|
+
if current_t:
|
|
278
|
+
print(f"Currently active trace: (ID: {current_t.trace_id})")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Flushing Tracing Data
|
|
282
|
+
|
|
283
|
+
Spans are generally batched and sent asynchronously by a background worker for efficiency. However, you might need to ensure all buffered spans are sent before an application exits or at critical points in your workflow (e.g., in a distributed worker setting).
|
|
284
|
+
|
|
285
|
+
You can force a synchronous flush of all queued spans using `flush_queue()` or on individual spans and traces (via their root span) with `span.flush()` & `trace.root_span.flush()`.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
import scale_gp_beta.lib.tracing as tracing
|
|
289
|
+
|
|
290
|
+
# ... (create some spans) ...
|
|
291
|
+
|
|
292
|
+
# Ensure all spans are sent before continuing
|
|
293
|
+
tracing.flush_queue()
|
|
294
|
+
print("All pending spans have been flushed.")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
You do not need to manually flush all spans on program exit; when shutting down the background worker, we will attempt to flush all tracing data before exiting.
|
|
298
|
+
|
|
299
|
+
#### Configuration Options
|
|
300
|
+
|
|
301
|
+
| ENV Variable | Description |
|
|
302
|
+
|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
303
|
+
| `DISABLE_SCALE_TRACING` | If set, no tracing data will be exported. You can still observe tracing data programmatically via the No-Op variant of Trace and Span objects. |
|
|
304
|
+
| `SGP_API_KEY` | SGP API Key. Used by `SGPClient`. |
|
|
305
|
+
| `SGP_ACCOUNT_ID` | SGP Account ID. Used by `SGPClient`. |
|
|
306
|
+
|
|
307
|
+
#### Multi-Process / Multi-Worker Tracing
|
|
308
|
+
|
|
309
|
+
> **_WARNING:_** Developers should be careful when attempting tracing over multiple workers / Python processes. The SGP backend will expect well-formed trace data, and there is a strong chance of race conditions if a child span is reported before a parent span.
|
|
310
|
+
|
|
311
|
+
The easiest approach to working over multiple workers and Python processes is to only create one trace per worker. You can group traces with a `group_id`.
|
|
312
|
+
|
|
313
|
+
If you want to track an entire workflow over multiple workers, ensure you call `tracing.flush_queue()` before you enqueue a job which creates child spans of the current trace.
|
|
314
|
+
|
|
315
|
+
You will need to use the explicit controls to forward trace and parent span IDs to your workers. The automatic context detection works within the context of the original Python process only.
|
|
316
|
+
|
|
317
|
+
|
|
80
318
|
## Async usage
|
|
81
319
|
|
|
82
320
|
Simply import `AsyncSGPClient` instead of `SGPClient` and use `await` with each API call:
|
|
@@ -340,7 +578,7 @@ client.with_options(max_retries=5).chat.completions.create(
|
|
|
340
578
|
### Timeouts
|
|
341
579
|
|
|
342
580
|
By default requests time out after 1 minute. You can configure this with a `timeout` option,
|
|
343
|
-
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
|
|
581
|
+
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
|
|
344
582
|
|
|
345
583
|
```python
|
|
346
584
|
from scale_gp_beta import SGPClient
|
|
@@ -11,7 +11,7 @@ scale_gp_beta/_resource.py,sha256=siZly_U6D0AOVLAzaOsqUdEFFzVMbWRj-ml30nvRp7E,11
|
|
|
11
11
|
scale_gp_beta/_response.py,sha256=GemuybPk0uemovTlGHyHkj-ScYTTDJA0jqH5FQqIPwQ,28852
|
|
12
12
|
scale_gp_beta/_streaming.py,sha256=fcCSGXslmi2SmmkM05g2SACXHk2Mj7k1X5uMBu6U5s8,10112
|
|
13
13
|
scale_gp_beta/_types.py,sha256=0wSs40TefKMPBj2wQKenEeZ0lzedoHClNJeqrpAgkII,6204
|
|
14
|
-
scale_gp_beta/_version.py,sha256=
|
|
14
|
+
scale_gp_beta/_version.py,sha256=PVHPnDC4m4CLgie5tfMTyuGdRwriv_lcwpf658BXOZ8,174
|
|
15
15
|
scale_gp_beta/pagination.py,sha256=t-U86PYxl20VRsz8VXOMJJDe7HxkX7ISFMvRNbBNy9s,4054
|
|
16
16
|
scale_gp_beta/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
scale_gp_beta/_utils/__init__.py,sha256=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
|
|
@@ -28,11 +28,11 @@ scale_gp_beta/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
|
|
|
28
28
|
scale_gp_beta/lib/tracing/__init__.py,sha256=UgyExbqAA2ljDEF4X4YFhtbBZuoQJ2IF4hkGs_xQEc0,226
|
|
29
29
|
scale_gp_beta/lib/tracing/exceptions.py,sha256=vL2_GAfWEy8EfLhrBkDClLYTasOLnL-5zUpdCQnSzcs,107
|
|
30
30
|
scale_gp_beta/lib/tracing/scope.py,sha256=kHrd0his8L2K_KXn2E6J9d565PliEdFoKRQ1d5ALTyk,3901
|
|
31
|
-
scale_gp_beta/lib/tracing/span.py,sha256=
|
|
32
|
-
scale_gp_beta/lib/tracing/trace.py,sha256=
|
|
31
|
+
scale_gp_beta/lib/tracing/span.py,sha256=VVibUzWAjENw80JH7JCMF2LbXr0aN9HNudduZBW0rx0,11260
|
|
32
|
+
scale_gp_beta/lib/tracing/trace.py,sha256=sdLUnvByLaMbV2TgI-MdKKs7If1-6GKz5j9A6scnDoI,6205
|
|
33
33
|
scale_gp_beta/lib/tracing/trace_exporter.py,sha256=bE6hS-Qu9KknEUTdsfIQMQwauah125mEavTDqEenBRA,3779
|
|
34
34
|
scale_gp_beta/lib/tracing/trace_queue_manager.py,sha256=xywP3myhaHX52i52ZfC2_-ONrd-4_aL-f3-3jNhh2XY,5961
|
|
35
|
-
scale_gp_beta/lib/tracing/tracing.py,sha256=
|
|
35
|
+
scale_gp_beta/lib/tracing/tracing.py,sha256=piSl0HsC442wcgjyBU7FLJszY0EoYaGxyRIy_uocR_o,8835
|
|
36
36
|
scale_gp_beta/lib/tracing/types.py,sha256=fnU7XGiyfF3UEIx-iqyHRjNHlOV7s75tP0b5efvt2sk,1156
|
|
37
37
|
scale_gp_beta/lib/tracing/util.py,sha256=8Oq4wLXRNOzh3CC1zRaBEr0h_WdXLrk536BUNKRddVE,1527
|
|
38
38
|
scale_gp_beta/resources/__init__.py,sha256=Fyo05_2_pc5orfyTSIpxa3btmBTd45VasgibwSqbbKo,4942
|
|
@@ -117,7 +117,7 @@ scale_gp_beta/types/chat/completion_models_params.py,sha256=ETxafJIUx4tTvkiR-ZCr
|
|
|
117
117
|
scale_gp_beta/types/chat/completion_models_response.py,sha256=Ctgj6o-QWPSdjBKzG9J4Id0-DjXu4UGGw1NR6-840Ec,403
|
|
118
118
|
scale_gp_beta/types/chat/model_definition.py,sha256=NNgopTm900GD0Zs2YHkcvoW67uKaWUKVyPbhKBHvKdQ,817
|
|
119
119
|
scale_gp_beta/types/files/__init__.py,sha256=OKfJYcKb4NObdiRObqJV_dOyDQ8feXekDUge2o_4pXQ,122
|
|
120
|
-
scale_gp_beta-0.1.
|
|
121
|
-
scale_gp_beta-0.1.
|
|
122
|
-
scale_gp_beta-0.1.
|
|
123
|
-
scale_gp_beta-0.1.
|
|
120
|
+
scale_gp_beta-0.1.0a23.dist-info/METADATA,sha256=EUWqFlefgJo-cCYXL1QNdQ-zhzw6XZaM1k8k0a9cmqw,27611
|
|
121
|
+
scale_gp_beta-0.1.0a23.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
122
|
+
scale_gp_beta-0.1.0a23.dist-info/licenses/LICENSE,sha256=x49Bj8r_ZpqfzThbmfHyZ_bE88XvHdIMI_ANyLHFFRE,11338
|
|
123
|
+
scale_gp_beta-0.1.0a23.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|