scale-gp-beta 0.1.0a20__py3-none-any.whl → 0.1.0a22__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/_base_client.py +16 -2
- scale_gp_beta/_version.py +1 -1
- scale_gp_beta/lib/tracing/span.py +8 -2
- scale_gp_beta/lib/tracing/trace.py +22 -0
- scale_gp_beta/lib/tracing/tracing.py +12 -1
- {scale_gp_beta-0.1.0a20.dist-info → scale_gp_beta-0.1.0a22.dist-info}/METADATA +239 -1
- {scale_gp_beta-0.1.0a20.dist-info → scale_gp_beta-0.1.0a22.dist-info}/RECORD +9 -9
- {scale_gp_beta-0.1.0a20.dist-info → scale_gp_beta-0.1.0a22.dist-info}/WHEEL +0 -0
- {scale_gp_beta-0.1.0a20.dist-info → scale_gp_beta-0.1.0a22.dist-info}/licenses/LICENSE +0 -0
scale_gp_beta/_base_client.py
CHANGED
|
@@ -1071,7 +1071,14 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
|
1071
1071
|
) -> ResponseT:
|
|
1072
1072
|
origin = get_origin(cast_to) or cast_to
|
|
1073
1073
|
|
|
1074
|
-
if
|
|
1074
|
+
if (
|
|
1075
|
+
inspect.isclass(origin)
|
|
1076
|
+
and issubclass(origin, BaseAPIResponse)
|
|
1077
|
+
# we only want to actually return the custom BaseAPIResponse class if we're
|
|
1078
|
+
# returning the raw response, or if we're not streaming SSE, as if we're streaming
|
|
1079
|
+
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
|
|
1080
|
+
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
|
|
1081
|
+
):
|
|
1075
1082
|
if not issubclass(origin, APIResponse):
|
|
1076
1083
|
raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
|
|
1077
1084
|
|
|
@@ -1574,7 +1581,14 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
|
1574
1581
|
) -> ResponseT:
|
|
1575
1582
|
origin = get_origin(cast_to) or cast_to
|
|
1576
1583
|
|
|
1577
|
-
if
|
|
1584
|
+
if (
|
|
1585
|
+
inspect.isclass(origin)
|
|
1586
|
+
and issubclass(origin, BaseAPIResponse)
|
|
1587
|
+
# we only want to actually return the custom BaseAPIResponse class if we're
|
|
1588
|
+
# returning the raw response, or if we're not streaming SSE, as if we're streaming
|
|
1589
|
+
# SSE then `cast_to` doesn't actively reflect the type we need to parse into
|
|
1590
|
+
and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
|
|
1591
|
+
):
|
|
1578
1592
|
if not issubclass(origin, AsyncAPIResponse):
|
|
1579
1593
|
raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
|
|
1580
1594
|
|
scale_gp_beta/_version.py
CHANGED
|
@@ -53,6 +53,7 @@ class BaseSpan:
|
|
|
53
53
|
queue_manager: Optional[TraceQueueManager] = None,
|
|
54
54
|
span_id: Optional[str] = None,
|
|
55
55
|
parent_span_id: Optional[str] = None,
|
|
56
|
+
group_id: Optional[str] = None,
|
|
56
57
|
input: Optional[SpanInputParam] = None,
|
|
57
58
|
output: Optional[SpanOutputParam] = None,
|
|
58
59
|
metadata: Optional[SpanMetadataParam] = None,
|
|
@@ -60,6 +61,7 @@ class BaseSpan:
|
|
|
60
61
|
):
|
|
61
62
|
self.name = name
|
|
62
63
|
self.trace_id = trace_id or "no_trace_id"
|
|
64
|
+
self.group_id = group_id
|
|
63
65
|
self.span_id: str = span_id or generate_span_id()
|
|
64
66
|
self.parent_span_id = parent_span_id
|
|
65
67
|
self.start_time: Optional[str] = None
|
|
@@ -124,6 +126,9 @@ class BaseSpan:
|
|
|
124
126
|
if self.parent_span_id is not None:
|
|
125
127
|
request_data["parent_id"] = self.parent_span_id
|
|
126
128
|
|
|
129
|
+
if self.group_id is not None:
|
|
130
|
+
request_data["group_id"] = self.group_id
|
|
131
|
+
|
|
127
132
|
return request_data
|
|
128
133
|
|
|
129
134
|
@override
|
|
@@ -134,6 +139,7 @@ class BaseSpan:
|
|
|
134
139
|
f"span_id='{self.span_id}', "
|
|
135
140
|
f"trace_id='{self.trace_id}', "
|
|
136
141
|
f"parent_span_id='{self.parent_span_id}', "
|
|
142
|
+
f"group_id='{self.group_id}', "
|
|
137
143
|
f"start_time='{self.start_time}', "
|
|
138
144
|
f"end_time='{self.end_time}', "
|
|
139
145
|
f"input='{self.input}', "
|
|
@@ -189,12 +195,13 @@ class Span(BaseSpan):
|
|
|
189
195
|
queue_manager: TraceQueueManager,
|
|
190
196
|
span_id: Optional[str] = None,
|
|
191
197
|
parent_span_id: Optional[str] = None,
|
|
198
|
+
group_id: Optional[str] = None,
|
|
192
199
|
input: Optional[SpanInputParam] = None,
|
|
193
200
|
output: Optional[SpanOutputParam] = None,
|
|
194
201
|
metadata: Optional[SpanMetadataParam] = None,
|
|
195
202
|
span_type: SpanTypeLiterals = "STANDALONE",
|
|
196
203
|
):
|
|
197
|
-
super().__init__(name, trace_id, queue_manager, span_id, parent_span_id, input, output, metadata, span_type)
|
|
204
|
+
super().__init__(name, trace_id, queue_manager, span_id, parent_span_id, group_id, input, output, metadata, span_type)
|
|
198
205
|
self._queue_manager: TraceQueueManager = queue_manager
|
|
199
206
|
self.trace_id: str = trace_id
|
|
200
207
|
|
|
@@ -206,7 +213,6 @@ class Span(BaseSpan):
|
|
|
206
213
|
The background worker batches and sends asynchronously.
|
|
207
214
|
:param blocking:
|
|
208
215
|
"""
|
|
209
|
-
# TODO: implement flush() for trace
|
|
210
216
|
if blocking:
|
|
211
217
|
self._queue_manager.export_now(self)
|
|
212
218
|
else:
|
|
@@ -27,6 +27,12 @@ class BaseTrace:
|
|
|
27
27
|
def end(self) -> None:
|
|
28
28
|
pass
|
|
29
29
|
|
|
30
|
+
def flush(self, blocking: bool = True) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def group_id(self) -> Optional[str]:
|
|
34
|
+
return None
|
|
35
|
+
|
|
30
36
|
def __enter__(self) -> "BaseTrace":
|
|
31
37
|
self.start()
|
|
32
38
|
return self
|
|
@@ -55,6 +61,7 @@ class NoOpTrace(BaseTrace):
|
|
|
55
61
|
queue_manager: Optional[TraceQueueManager] = None,
|
|
56
62
|
trace_id: Optional[str] = None,
|
|
57
63
|
span_id: Optional[str] = None,
|
|
64
|
+
group_id: Optional[str] = None,
|
|
58
65
|
span_type: SpanTypeLiterals = "TRACER",
|
|
59
66
|
input: Optional[SpanInputParam] = None,
|
|
60
67
|
output: Optional[SpanOutputParam] = None,
|
|
@@ -66,6 +73,7 @@ class NoOpTrace(BaseTrace):
|
|
|
66
73
|
name=name,
|
|
67
74
|
span_id=span_id,
|
|
68
75
|
trace_id=self.trace_id,
|
|
76
|
+
group_id=group_id,
|
|
69
77
|
queue_manager=queue_manager,
|
|
70
78
|
metadata=metadata,
|
|
71
79
|
span_type=span_type,
|
|
@@ -90,6 +98,10 @@ class NoOpTrace(BaseTrace):
|
|
|
90
98
|
def end(self) -> None:
|
|
91
99
|
self.root_span.end()
|
|
92
100
|
|
|
101
|
+
@override
|
|
102
|
+
def group_id(self) -> Optional[str]:
|
|
103
|
+
return self.root_span.group_id
|
|
104
|
+
|
|
93
105
|
|
|
94
106
|
class Trace(BaseTrace):
|
|
95
107
|
def __init__(
|
|
@@ -98,6 +110,7 @@ class Trace(BaseTrace):
|
|
|
98
110
|
queue_manager: TraceQueueManager,
|
|
99
111
|
trace_id: Optional[str] = None,
|
|
100
112
|
span_id: Optional[str] = None,
|
|
113
|
+
group_id: Optional[str] = None,
|
|
101
114
|
span_type: SpanTypeLiterals = "TRACER",
|
|
102
115
|
input: Optional[SpanInputParam] = None,
|
|
103
116
|
output: Optional[SpanOutputParam] = None,
|
|
@@ -110,6 +123,7 @@ class Trace(BaseTrace):
|
|
|
110
123
|
name=name,
|
|
111
124
|
span_id=span_id,
|
|
112
125
|
trace_id=self.trace_id,
|
|
126
|
+
group_id=group_id,
|
|
113
127
|
queue_manager=queue_manager,
|
|
114
128
|
metadata=metadata,
|
|
115
129
|
span_type=span_type,
|
|
@@ -145,6 +159,14 @@ class Trace(BaseTrace):
|
|
|
145
159
|
|
|
146
160
|
self.root_span.end()
|
|
147
161
|
|
|
162
|
+
@override
|
|
163
|
+
def flush(self, blocking: bool = True) -> None:
|
|
164
|
+
self.root_span.flush(blocking=blocking)
|
|
165
|
+
|
|
166
|
+
@override
|
|
167
|
+
def group_id(self) -> Optional[str]:
|
|
168
|
+
return self.root_span.group_id
|
|
169
|
+
|
|
148
170
|
@override
|
|
149
171
|
def __repr__(self) -> str:
|
|
150
172
|
return (
|
|
@@ -57,6 +57,7 @@ def create_trace(
|
|
|
57
57
|
metadata: Optional[SpanMetadataParam] = None,
|
|
58
58
|
span_id: Optional[str] = None,
|
|
59
59
|
trace_id: Optional[str] = None,
|
|
60
|
+
group_id: Optional[str] = None,
|
|
60
61
|
) -> BaseTrace:
|
|
61
62
|
"""Creates a new trace and root span instance.
|
|
62
63
|
|
|
@@ -81,6 +82,7 @@ def create_trace(
|
|
|
81
82
|
trace_id (Optional[str]): An optional, user-defined ID for the trace.
|
|
82
83
|
If None, a unique trace ID will be generated.
|
|
83
84
|
Max length is 38 characters.
|
|
85
|
+
group_id (Optional[str]): An optional, id to group traces.
|
|
84
86
|
|
|
85
87
|
Returns:
|
|
86
88
|
BaseTrace: A `Trace` instance if tracing is enabled, or a `NoOpTrace`
|
|
@@ -96,6 +98,7 @@ def create_trace(
|
|
|
96
98
|
name=name,
|
|
97
99
|
trace_id=trace_id,
|
|
98
100
|
span_id=span_id,
|
|
101
|
+
group_id=group_id,
|
|
99
102
|
span_type=span_type,
|
|
100
103
|
input=impl_input,
|
|
101
104
|
output=impl_output,
|
|
@@ -111,6 +114,7 @@ def create_trace(
|
|
|
111
114
|
name=name,
|
|
112
115
|
trace_id=trace_id,
|
|
113
116
|
span_id=span_id,
|
|
117
|
+
group_id=group_id,
|
|
114
118
|
queue_manager=queue_manager,
|
|
115
119
|
span_type=span_type,
|
|
116
120
|
input=impl_input,
|
|
@@ -131,6 +135,7 @@ def create_span(
|
|
|
131
135
|
span_id: Optional[str] = None,
|
|
132
136
|
parent_id: Optional[str] = None,
|
|
133
137
|
trace_id: Optional[str] = None,
|
|
138
|
+
group_id: Optional[str] = None,
|
|
134
139
|
) -> BaseSpan:
|
|
135
140
|
"""Creates a new span instance.
|
|
136
141
|
|
|
@@ -164,7 +169,8 @@ def create_span(
|
|
|
164
169
|
parent_id (Optional[str], optional): A `Span` id. Used for explicit control.
|
|
165
170
|
Defaults to span id fetched from the active scope.
|
|
166
171
|
trace_id (Optional[str], optional): A `Trace` id. Used for explicit control.
|
|
167
|
-
|
|
172
|
+
Default to trace id fetched from the active scope.
|
|
173
|
+
group_id (Optional[str]): An optional, id to group traces.
|
|
168
174
|
|
|
169
175
|
Returns:
|
|
170
176
|
BaseSpan: A `Span` instance if tracing is enabled and a valid trace context
|
|
@@ -176,12 +182,15 @@ def create_span(
|
|
|
176
182
|
|
|
177
183
|
scoped_trace = current_trace()
|
|
178
184
|
scoped_trace_id = scoped_trace.trace_id if scoped_trace else None
|
|
185
|
+
scoped_group_id = scoped_trace.group_id() if scoped_trace else None
|
|
179
186
|
scoped_span = current_span()
|
|
180
187
|
scoped_span_id = scoped_span.span_id if scoped_span else None
|
|
181
188
|
|
|
182
189
|
parent_span_id: Optional[str] = parent_id or scoped_span_id
|
|
183
190
|
# TODO: preference should be trace_id -> trace_id from parent span if parent_id present -> scoped_trace_id
|
|
191
|
+
# group_id -> group_id from trace if trace_id specified -> group_id from span if span_id -> scoped_group_id
|
|
184
192
|
impl_trace_id: Optional[str] = trace_id or scoped_trace_id
|
|
193
|
+
impl_group_id: Optional[str] = group_id or scoped_group_id
|
|
185
194
|
|
|
186
195
|
# TODO: do a check to ensure trace_id of parent_span matches trace_id (when trace_id is specified)
|
|
187
196
|
|
|
@@ -190,6 +199,7 @@ def create_span(
|
|
|
190
199
|
span_id=span_id,
|
|
191
200
|
parent_span_id=parent_span_id,
|
|
192
201
|
trace_id=impl_trace_id,
|
|
202
|
+
group_id=impl_group_id,
|
|
193
203
|
input=impl_input,
|
|
194
204
|
output=impl_output,
|
|
195
205
|
metadata=impl_metadata,
|
|
@@ -209,6 +219,7 @@ def create_span(
|
|
|
209
219
|
span_id=span_id,
|
|
210
220
|
parent_span_id=parent_span_id,
|
|
211
221
|
trace_id=impl_trace_id,
|
|
222
|
+
group_id=impl_group_id,
|
|
212
223
|
input=impl_input,
|
|
213
224
|
output=impl_output,
|
|
214
225
|
metadata=impl_metadata,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scale-gp-beta
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a22
|
|
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
|
|
@@ -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:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
scale_gp_beta/__init__.py,sha256=x8toPf2r1c14qt9Fvh5cjeX1tytmAWa3lMc4tg9SYKY,2646
|
|
2
|
-
scale_gp_beta/_base_client.py,sha256=
|
|
2
|
+
scale_gp_beta/_base_client.py,sha256=u6z6Qxu3eqzfbcDx-zlIUtbuPWF9MgeygEi6H8HN4X8,65891
|
|
3
3
|
scale_gp_beta/_client.py,sha256=3pNXJs9TKLjfYoTjFIIoSdd4mwwX1cc-VUveMBmkl_8,24491
|
|
4
4
|
scale_gp_beta/_compat.py,sha256=VWemUKbj6DDkQ-O4baSpHVLJafotzeXmCQGJugfVTIw,6580
|
|
5
5
|
scale_gp_beta/_constants.py,sha256=S14PFzyN9-I31wiV7SmIlL5Ga0MLHxdvegInGdXH7tM,462
|
|
@@ -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=JLVPWU9jvr2kcPJpjRIxvVmQjhYdVN2g4Wjh0V9d8zc,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=_XocyodHFJsIihEOyFKtdojdsUR2wayqoRL7Kj60PVQ,9671
|
|
32
|
+
scale_gp_beta/lib/tracing/trace.py,sha256=GEzE78sulYKooz46vtZ7JI77_KDCOpQBfYsahZE_7HU,5084
|
|
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=kGoUo3Kf172iJudzFIomXvQ_2aiWPr_38wqbKFX4rGo,8837
|
|
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.0a22.dist-info/METADATA,sha256=Ex9RXx9qwGNdoyA56V6KvsQb4Z0FVQRJHN8i-irCkqI,27524
|
|
121
|
+
scale_gp_beta-0.1.0a22.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
122
|
+
scale_gp_beta-0.1.0a22.dist-info/licenses/LICENSE,sha256=x49Bj8r_ZpqfzThbmfHyZ_bE88XvHdIMI_ANyLHFFRE,11338
|
|
123
|
+
scale_gp_beta-0.1.0a22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|