dreadnode 1.0.0rc3__tar.gz → 1.0.2__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 (29) hide show
  1. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/PKG-INFO +1 -2
  2. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/api/client.py +61 -14
  3. dreadnode-1.0.2/dreadnode/api/models.py +304 -0
  4. dreadnode-1.0.2/dreadnode/api/util.py +173 -0
  5. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/integrations/transformers.py +3 -3
  6. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/main.py +29 -8
  7. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/object.py +3 -0
  8. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/task.py +51 -13
  9. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/tracing/span.py +29 -11
  10. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/types.py +8 -0
  11. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/pyproject.toml +3 -4
  12. dreadnode-1.0.0rc3/dreadnode/api/models.py +0 -210
  13. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/README.md +0 -0
  14. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/__init__.py +0 -0
  15. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/api/__init__.py +0 -0
  16. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/artifact/__init__.py +0 -0
  17. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/artifact/merger.py +0 -0
  18. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/artifact/storage.py +0 -0
  19. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/artifact/tree_builder.py +0 -0
  20. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/constants.py +0 -0
  21. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/integrations/__init__.py +0 -0
  22. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/metric.py +0 -0
  23. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/py.typed +0 -0
  24. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/serialization.py +0 -0
  25. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/tracing/__init__.py +0 -0
  26. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/tracing/constants.py +0 -0
  27. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/tracing/exporters.py +0 -0
  28. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/util.py +0 -0
  29. {dreadnode-1.0.0rc3 → dreadnode-1.0.2}/dreadnode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dreadnode
3
- Version: 1.0.0rc3
3
+ Version: 1.0.2
4
4
  Summary: Dreadnode SDK
5
5
  Author: Nick Landers
6
6
  Author-email: monoxgas@gmail.com
@@ -12,7 +12,6 @@ Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Provides-Extra: training
14
14
  Requires-Dist: coolname (>=2.2.0,<3.0.0)
15
- Requires-Dist: fast-depends (>=2.4.12,<3.0.0)
16
15
  Requires-Dist: fsspec[s3] (>=2023.1.0,<=2025.3.0)
17
16
  Requires-Dist: httpx (>=0.28.0,<0.29.0)
18
17
  Requires-Dist: logfire (>=3.5.3,<4.0.0)
@@ -7,18 +7,29 @@ import pandas as pd
7
7
  from pydantic import BaseModel
8
8
  from ulid import ULID
9
9
 
10
+ from dreadnode.api.util import (
11
+ convert_flat_tasks_to_tree,
12
+ convert_flat_trace_to_tree,
13
+ process_run,
14
+ process_task,
15
+ )
10
16
  from dreadnode.util import logger
11
17
  from dreadnode.version import VERSION
12
18
 
13
19
  from .models import (
14
20
  MetricAggregationType,
15
21
  Project,
22
+ RawRun,
23
+ RawTask,
16
24
  Run,
25
+ RunSummary,
17
26
  StatusFilter,
18
27
  Task,
28
+ TaskTree,
19
29
  TimeAggregationType,
20
30
  TimeAxisType,
21
31
  TraceSpan,
32
+ TraceTree,
22
33
  UserDataCredentials,
23
34
  )
24
35
 
@@ -119,27 +130,63 @@ class ApiClient:
119
130
  response = self.request("GET", f"/strikes/projects/{project!s}")
120
131
  return Project(**response.json())
121
132
 
122
- def list_runs(self, project: str) -> list[Run]:
133
+ def list_runs(self, project: str) -> list[RunSummary]:
123
134
  response = self.request("GET", f"/strikes/projects/{project!s}/runs")
124
- return [Run(**run) for run in response.json()]
135
+ return [RunSummary(**run) for run in response.json()]
125
136
 
126
- def get_run(self, run: str | ULID) -> Run:
137
+ def _get_run(self, run: str | ULID) -> RawRun:
127
138
  response = self.request("GET", f"/strikes/projects/runs/{run!s}")
128
- return Run(**response.json())
129
-
130
- def get_run_tasks(self, run: str | ULID) -> list[Task]:
131
- response = self.request("GET", f"/strikes/projects/runs/{run!s}/tasks")
132
- return [Task(**task) for task in response.json()]
139
+ return RawRun(**response.json())
133
140
 
134
- def get_run_trace(self, run: str | ULID) -> list[Task | TraceSpan]:
135
- response = self.request("GET", f"/strikes/projects/runs/{run!s}/spans")
136
- spans: list[Task | TraceSpan] = []
141
+ def get_run(self, run: str | ULID) -> Run:
142
+ return process_run(self._get_run(run))
143
+
144
+ TraceFormat = t.Literal["tree", "flat"]
145
+
146
+ @t.overload
147
+ def get_run_tasks(
148
+ self, run: str | ULID, *, format: t.Literal["tree"] = "tree"
149
+ ) -> list[TaskTree]: ...
150
+
151
+ @t.overload
152
+ def get_run_tasks(
153
+ self, run: str | ULID, *, format: t.Literal["flat"] = "flat"
154
+ ) -> list[Task]: ...
155
+
156
+ def get_run_tasks(
157
+ self, run: str | ULID, *, format: TraceFormat = "flat"
158
+ ) -> list[Task] | list[TaskTree]:
159
+ raw_run = self._get_run(run)
160
+ response = self.request("GET", f"/strikes/projects/runs/{run!s}/tasks/full")
161
+ raw_tasks = [RawTask(**task) for task in response.json()]
162
+ tasks = [process_task(task, raw_run) for task in raw_tasks]
163
+ tasks = sorted(tasks, key=lambda x: x.timestamp)
164
+ return tasks if format == "flat" else convert_flat_tasks_to_tree(tasks)
165
+
166
+ @t.overload
167
+ def get_run_trace(
168
+ self, run: str | ULID, *, format: t.Literal["tree"] = "tree"
169
+ ) -> list[TraceTree]: ...
170
+
171
+ @t.overload
172
+ def get_run_trace(
173
+ self, run: str | ULID, *, format: t.Literal["flat"] = "flat"
174
+ ) -> list[Task | TraceSpan]: ...
175
+
176
+ def get_run_trace(
177
+ self, run: str | ULID, *, format: TraceFormat = "flat"
178
+ ) -> list[Task | TraceSpan] | list[TraceTree]:
179
+ raw_run = self._get_run(run)
180
+ response = self.request("GET", f"/strikes/projects/runs/{run!s}/spans/full")
181
+ trace: list[Task | TraceSpan] = []
137
182
  for item in response.json():
138
183
  if "parent_task_span_id" in item:
139
- spans.append(Task(**item))
184
+ trace.append(process_task(RawTask(**item), raw_run))
140
185
  else:
141
- spans.append(TraceSpan(**item))
142
- return spans
186
+ trace.append(TraceSpan(**item))
187
+
188
+ trace = sorted(trace, key=lambda x: x.timestamp)
189
+ return trace if format == "flat" else convert_flat_trace_to_tree(trace)
143
190
 
144
191
  # Data exports
145
192
 
@@ -0,0 +1,304 @@
1
+ import contextlib
2
+ import typing as t
3
+ from datetime import datetime
4
+ from functools import cached_property
5
+ from uuid import UUID
6
+
7
+ import requests
8
+ from pydantic import (
9
+ BaseModel,
10
+ ConfigDict,
11
+ Field,
12
+ PrivateAttr,
13
+ TypeAdapter,
14
+ ValidationError,
15
+ field_validator,
16
+ )
17
+ from ulid import ULID
18
+
19
+ AnyDict = dict[str, t.Any]
20
+
21
+ # User
22
+
23
+
24
+ class UserAPIKey(BaseModel):
25
+ key: str
26
+
27
+
28
+ class UserResponse(BaseModel):
29
+ id: UUID
30
+ email_address: str
31
+ username: str
32
+ api_key: UserAPIKey
33
+
34
+
35
+ # Strikes
36
+
37
+ SpanStatus = t.Literal[
38
+ "pending", # A pending span has been created
39
+ "completed", # The span has been finished
40
+ "failed", # The raised an exception
41
+ ]
42
+
43
+ ExportFormat = t.Literal["csv", "json", "jsonl", "parquet"]
44
+ StatusFilter = t.Literal["all", "completed", "failed"]
45
+ TimeAxisType = t.Literal["wall", "relative", "step"]
46
+ TimeAggregationType = t.Literal["max", "min", "sum", "count"]
47
+ MetricAggregationType = t.Literal[
48
+ "avg",
49
+ "median",
50
+ "min",
51
+ "max",
52
+ "sum",
53
+ "first",
54
+ "last",
55
+ "count",
56
+ "std",
57
+ "var",
58
+ ]
59
+
60
+
61
+ class SpanException(BaseModel):
62
+ type: str
63
+ message: str
64
+ stacktrace: str
65
+
66
+
67
+ class SpanEvent(BaseModel):
68
+ timestamp: datetime
69
+ name: str
70
+ attributes: AnyDict
71
+
72
+
73
+ class SpanLink(BaseModel):
74
+ trace_id: str
75
+ span_id: str
76
+ attributes: AnyDict
77
+
78
+
79
+ class TraceLog(BaseModel):
80
+ timestamp: datetime
81
+ body: str
82
+ severity: str
83
+ service: str | None
84
+ trace_id: str | None
85
+ span_id: str | None
86
+ attributes: AnyDict
87
+ container: str | None
88
+
89
+
90
+ class TraceSpan(BaseModel):
91
+ timestamp: datetime
92
+ duration: int
93
+ trace_id: str = Field(repr=False)
94
+ span_id: str
95
+ parent_span_id: str | None = Field(repr=False)
96
+ service_name: str | None = Field(repr=False)
97
+ status: SpanStatus
98
+ exception: SpanException | None
99
+ name: str
100
+ attributes: AnyDict = Field(repr=False)
101
+ resource_attributes: AnyDict = Field(repr=False)
102
+ events: list[SpanEvent] = Field(repr=False)
103
+ links: list[SpanLink] = Field(repr=False)
104
+
105
+
106
+ class Metric(BaseModel):
107
+ value: float
108
+ step: int
109
+ timestamp: datetime
110
+ attributes: AnyDict
111
+
112
+
113
+ class ObjectRef(BaseModel):
114
+ name: str
115
+ label: str
116
+ hash: str
117
+
118
+
119
+ class RawObjectUri(BaseModel):
120
+ hash: str
121
+ schema_hash: str
122
+ uri: str
123
+ size: int
124
+ type: t.Literal["uri"]
125
+
126
+
127
+ class RawObjectVal(BaseModel):
128
+ hash: str
129
+ schema_hash: str
130
+ value: t.Any
131
+ type: t.Literal["val"]
132
+
133
+
134
+ RawObject = RawObjectUri | RawObjectVal
135
+
136
+
137
+ class V0Object(BaseModel):
138
+ name: str
139
+ label: str
140
+ value: t.Any
141
+
142
+
143
+ class ObjectVal(BaseModel):
144
+ model_config = ConfigDict(arbitrary_types_allowed=True)
145
+
146
+ name: str
147
+ label: str
148
+ hash: str = Field(repr=False)
149
+ schema_: AnyDict
150
+ schema_hash: str = Field(repr=False)
151
+ value: t.Any
152
+
153
+ @field_validator("value")
154
+ @classmethod
155
+ def validate_value(cls, value: t.Any) -> t.Any:
156
+ if isinstance(value, str):
157
+ with contextlib.suppress(ValidationError):
158
+ return TypeAdapter(t.Any).validate_json(value)
159
+
160
+ return value
161
+
162
+
163
+ class ObjectUri(BaseModel):
164
+ name: str
165
+ label: str
166
+ hash: str = Field(repr=False)
167
+ schema_: AnyDict
168
+ schema_hash: str = Field(repr=False)
169
+ uri: str
170
+ size: int
171
+
172
+ _value: t.Any = PrivateAttr(default=None)
173
+
174
+ @cached_property
175
+ def value(self) -> t.Any:
176
+ if self._value is not None:
177
+ return self._value
178
+
179
+ try:
180
+ response = requests.get(self.uri, timeout=5)
181
+ response.raise_for_status()
182
+ self._value = response.text
183
+ except requests.RequestException as e:
184
+ raise RuntimeError(f"Failed to fetch object from {self.uri}") from e
185
+
186
+ if isinstance(self._value, str):
187
+ with contextlib.suppress(ValidationError):
188
+ self._value = TypeAdapter(t.Any).validate_json(self._value)
189
+
190
+ return self._value
191
+
192
+
193
+ Object = ObjectVal | ObjectUri
194
+
195
+
196
+ class ArtifactFile(BaseModel):
197
+ hash: str
198
+ uri: str
199
+ size_bytes: int
200
+ final_real_path: str
201
+
202
+
203
+ class ArtifactDir(BaseModel):
204
+ dir_path: str
205
+ hash: str
206
+ children: list[t.Union["ArtifactDir", ArtifactFile]]
207
+
208
+
209
+ class RunSummary(BaseModel):
210
+ id: ULID
211
+ name: str
212
+ span_id: str = Field(repr=False)
213
+ trace_id: str = Field(repr=False)
214
+ timestamp: datetime
215
+ duration: int
216
+ status: SpanStatus
217
+ exception: SpanException | None
218
+ tags: set[str]
219
+ params: AnyDict = Field(repr=False)
220
+ metrics: dict[str, list[Metric]] = Field(repr=False)
221
+
222
+
223
+ class RawRun(RunSummary):
224
+ inputs: list[ObjectRef] = Field(repr=False)
225
+ outputs: list[ObjectRef] = Field(repr=False)
226
+ objects: dict[str, RawObject] = Field(repr=False)
227
+ object_schemas: AnyDict = Field(repr=False)
228
+ artifacts: list[ArtifactDir] = Field(repr=False)
229
+ schema_: AnyDict = Field(alias="schema", repr=False)
230
+
231
+
232
+ class Run(RunSummary):
233
+ inputs: dict[str, Object] = Field(repr=False)
234
+ outputs: dict[str, Object] = Field(repr=False)
235
+ artifacts: list[ArtifactDir] = Field(repr=False)
236
+ schema_: AnyDict = Field(alias="schema", repr=False)
237
+
238
+
239
+ class _Task(BaseModel):
240
+ name: str
241
+ span_id: str
242
+ trace_id: str = Field(repr=False)
243
+ parent_span_id: str | None = Field(repr=False)
244
+ parent_task_span_id: str | None = Field(repr=False)
245
+ timestamp: datetime
246
+ duration: int
247
+ status: SpanStatus
248
+ exception: SpanException | None
249
+ tags: set[str]
250
+ params: AnyDict = Field(repr=False)
251
+ metrics: dict[str, list[Metric]] = Field(repr=False)
252
+ schema_: AnyDict = Field(alias="schema", repr=False)
253
+ attributes: AnyDict = Field(repr=False)
254
+ resource_attributes: AnyDict = Field(repr=False)
255
+ events: list[SpanEvent] = Field(repr=False)
256
+ links: list[SpanLink] = Field(repr=False)
257
+
258
+
259
+ class RawTask(_Task):
260
+ inputs: list[ObjectRef] | list[V0Object] = Field(repr=False)
261
+ outputs: list[ObjectRef] | list[V0Object] = Field(repr=False)
262
+
263
+
264
+ class Task(_Task):
265
+ inputs: dict[str, Object] = Field(repr=False)
266
+ outputs: dict[str, Object] = Field(repr=False)
267
+
268
+
269
+ class Project(BaseModel):
270
+ id: UUID = Field(repr=False)
271
+ key: str
272
+ name: str
273
+ description: str | None = Field(repr=False)
274
+ created_at: datetime
275
+ updated_at: datetime
276
+ run_count: int
277
+ last_run: RawRun | None = Field(repr=False)
278
+
279
+
280
+ # Derived types
281
+
282
+
283
+ class TaskTree(BaseModel):
284
+ task: Task
285
+ children: list["TaskTree"] = []
286
+
287
+
288
+ class TraceTree(BaseModel):
289
+ span: Task | TraceSpan
290
+ children: list["TraceTree"] = []
291
+
292
+
293
+ # User data credentials
294
+
295
+
296
+ class UserDataCredentials(BaseModel):
297
+ access_key_id: str
298
+ secret_access_key: str
299
+ session_token: str
300
+ expiration: datetime
301
+ region: str
302
+ bucket: str
303
+ prefix: str
304
+ endpoint: str | None
@@ -0,0 +1,173 @@
1
+ from logging import getLogger
2
+
3
+ from .models import (
4
+ Object,
5
+ ObjectUri,
6
+ ObjectVal,
7
+ RawObjectVal,
8
+ RawRun,
9
+ RawTask,
10
+ Run,
11
+ Task,
12
+ TaskTree,
13
+ TraceSpan,
14
+ TraceTree,
15
+ V0Object,
16
+ )
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ def convert_flat_tasks_to_tree(tasks: list[Task]) -> list[TaskTree]:
22
+ tree_nodes: dict[str, TaskTree] = {}
23
+ for task in tasks:
24
+ tree_nodes[task.span_id] = TaskTree(task=task)
25
+
26
+ roots: list[TaskTree] = []
27
+ for task in tasks:
28
+ if task.parent_task_span_id not in tree_nodes:
29
+ roots.append(tree_nodes[task.span_id])
30
+ else:
31
+ parent_node = tree_nodes.get(task.parent_task_span_id)
32
+ if parent_node:
33
+ parent_node.children.append(tree_nodes[task.span_id])
34
+
35
+ return roots
36
+
37
+
38
+ def convert_flat_trace_to_tree(trace: list[Task | TraceSpan]) -> list[TraceTree]:
39
+ tree_nodes: dict[str, TraceTree] = {}
40
+ for span in trace:
41
+ tree_nodes[span.span_id] = TraceTree(span=span)
42
+
43
+ roots: list[TraceTree] = []
44
+ for span in trace:
45
+ if span.parent_span_id not in tree_nodes:
46
+ roots.append(tree_nodes[span.span_id])
47
+ else:
48
+ parent_node = tree_nodes.get(span.parent_span_id)
49
+ if parent_node:
50
+ parent_node.children.append(tree_nodes[span.span_id])
51
+
52
+ return roots
53
+
54
+
55
+ def process_run(run: RawRun) -> Run:
56
+ inputs: dict[str, Object] = {}
57
+ outputs: dict[str, Object] = {}
58
+
59
+ for references, converted in ((run.inputs, inputs), (run.outputs, outputs)):
60
+ for ref in references:
61
+ if (_object := run.objects.get(ref.hash)) is None:
62
+ logger.error("Object %s not found in run %s", ref.hash, run.id)
63
+ continue
64
+
65
+ if (_schema := run.object_schemas.get(_object.schema_hash)) is None:
66
+ logger.error("Schema for object %s not found in run %s", ref.hash, run.id)
67
+ continue
68
+
69
+ if isinstance(_object, RawObjectVal):
70
+ converted[ref.name] = ObjectVal(
71
+ name=ref.name,
72
+ label=ref.label,
73
+ hash=ref.hash,
74
+ schema_=_schema,
75
+ schema_hash=_object.schema_hash,
76
+ value=_object.value,
77
+ )
78
+ else:
79
+ converted[ref.name] = ObjectUri(
80
+ name=ref.name,
81
+ label=ref.label,
82
+ hash=ref.hash,
83
+ schema_=_schema,
84
+ schema_hash=_object.schema_hash,
85
+ uri=_object.uri,
86
+ size=_object.size,
87
+ )
88
+
89
+ return Run(
90
+ id=run.id,
91
+ name=run.name,
92
+ span_id=run.span_id,
93
+ trace_id=run.trace_id,
94
+ timestamp=run.timestamp,
95
+ duration=run.duration,
96
+ status=run.status,
97
+ exception=run.exception,
98
+ tags=run.tags,
99
+ params=run.params,
100
+ metrics=run.metrics,
101
+ inputs=inputs,
102
+ outputs=outputs,
103
+ artifacts=run.artifacts,
104
+ schema=run.schema_,
105
+ )
106
+
107
+
108
+ def process_task(task: RawTask, run: RawRun) -> Task:
109
+ inputs: dict[str, Object] = {}
110
+ outputs: dict[str, Object] = {}
111
+
112
+ for references, converted in ((task.inputs, inputs), (task.outputs, outputs)):
113
+ for ref in references:
114
+ if isinstance(ref, V0Object):
115
+ converted[ref.name] = ObjectVal(
116
+ name=ref.name,
117
+ label=ref.label,
118
+ hash="",
119
+ schema_={},
120
+ schema_hash="",
121
+ value=ref.value,
122
+ )
123
+ continue
124
+
125
+ if (_object := run.objects.get(ref.hash)) is None:
126
+ logger.error("Object %s not found in run %s", ref.hash, run.id)
127
+ continue
128
+
129
+ if (_schema := run.object_schemas.get(_object.schema_hash)) is None:
130
+ logger.error("Schema for object %s not found in run %s", ref.hash, run.id)
131
+ continue
132
+
133
+ if isinstance(_object, RawObjectVal):
134
+ converted[ref.name] = ObjectVal(
135
+ name=ref.name,
136
+ label=ref.label,
137
+ hash=ref.hash,
138
+ schema_=_schema,
139
+ schema_hash=_object.schema_hash,
140
+ value=_object.value,
141
+ )
142
+ else:
143
+ converted[ref.name] = ObjectUri(
144
+ name=ref.name,
145
+ label=ref.label,
146
+ hash=ref.hash,
147
+ schema_=_schema,
148
+ schema_hash=_object.schema_hash,
149
+ uri=_object.uri,
150
+ size=_object.size,
151
+ )
152
+
153
+ return Task(
154
+ name=task.name,
155
+ span_id=task.span_id,
156
+ trace_id=task.trace_id,
157
+ parent_span_id=task.parent_span_id,
158
+ parent_task_span_id=task.parent_task_span_id,
159
+ timestamp=task.timestamp,
160
+ duration=task.duration,
161
+ status=task.status,
162
+ exception=task.exception,
163
+ tags=task.tags,
164
+ params=task.params,
165
+ metrics=task.metrics,
166
+ inputs=inputs,
167
+ outputs=outputs,
168
+ schema=task.schema_,
169
+ attributes=task.attributes,
170
+ resource_attributes=task.resource_attributes,
171
+ events=task.events,
172
+ links=task.links,
173
+ )
@@ -123,7 +123,7 @@ class DreadnodeCallback(TrainerCallback):
123
123
  if self._run is None or state.epoch is None:
124
124
  return
125
125
 
126
- dn.log_metric("epoch", state.epoch)
126
+ dn.log_metric("epoch", state.epoch, to="run")
127
127
 
128
128
  self._epoch_span = dn.task_span(f"Epoch {state.epoch}")
129
129
  self._epoch_span.__enter__()
@@ -149,7 +149,7 @@ class DreadnodeCallback(TrainerCallback):
149
149
  if self._run is None:
150
150
  return
151
151
 
152
- dn.log_metric("step", state.global_step)
152
+ dn.log_metric("step", state.global_step, to="run")
153
153
 
154
154
  self._step_span = dn.span(f"Step {state.global_step}")
155
155
  self._step_span.__enter__()
@@ -178,6 +178,6 @@ class DreadnodeCallback(TrainerCallback):
178
178
 
179
179
  for key, value in _clean_keys(logs).items():
180
180
  if isinstance(value, float | int):
181
- dn.log_metric(key, value, step=state.global_step)
181
+ dn.log_metric(key, value, step=state.global_step, to="run")
182
182
 
183
183
  dn.push_update()
@@ -7,7 +7,7 @@ import typing as t
7
7
  from dataclasses import dataclass
8
8
  from datetime import datetime, timezone
9
9
  from pathlib import Path
10
- from urllib.parse import urljoin
10
+ from urllib.parse import urljoin, urlparse, urlunparse
11
11
 
12
12
  import coolname # type: ignore [import-untyped]
13
13
  import logfire
@@ -47,7 +47,10 @@ from dreadnode.tracing.span import (
47
47
  current_task_span,
48
48
  )
49
49
  from dreadnode.types import (
50
+ INHERITED,
50
51
  AnyDict,
52
+ Inherited,
53
+ JsonDict,
51
54
  JsonValue,
52
55
  )
53
56
  from dreadnode.util import handle_internal_errors
@@ -204,6 +207,16 @@ class Dreadnode:
204
207
  category=DreadnodeConfigWarning,
205
208
  )
206
209
 
210
+ if self.server:
211
+ parsed_url = urlparse(self.server)
212
+ if not parsed_url.scheme:
213
+ netloc = parsed_url.path.split("/")[0]
214
+ path = "/".join(parsed_url.path.split("/")[1:])
215
+ parsed_new = parsed_url._replace(
216
+ scheme="https", netloc=netloc, path=f"/{path}" if path else ""
217
+ )
218
+ self.server = urlunparse(parsed_new)
219
+
207
220
  if self.local_dir is not False:
208
221
  config = FileExportConfig(
209
222
  base_path=self.local_dir,
@@ -411,8 +424,8 @@ class Dreadnode:
411
424
  name: str | None = None,
412
425
  label: str | None = None,
413
426
  log_params: t.Sequence[str] | bool = False,
414
- log_inputs: t.Sequence[str] | bool = True,
415
- log_output: bool = True,
427
+ log_inputs: t.Sequence[str] | bool | Inherited = INHERITED,
428
+ log_output: bool | Inherited = INHERITED,
416
429
  tags: t.Sequence[str] | None = None,
417
430
  **attributes: t.Any,
418
431
  ) -> TaskDecorator: ...
@@ -425,8 +438,8 @@ class Dreadnode:
425
438
  name: str | None = None,
426
439
  label: str | None = None,
427
440
  log_params: t.Sequence[str] | bool = False,
428
- log_inputs: t.Sequence[str] | bool = True,
429
- log_output: bool = True,
441
+ log_inputs: t.Sequence[str] | bool | Inherited = INHERITED,
442
+ log_output: bool | Inherited = INHERITED,
430
443
  tags: t.Sequence[str] | None = None,
431
444
  **attributes: t.Any,
432
445
  ) -> ScoredTaskDecorator[R]: ...
@@ -438,8 +451,8 @@ class Dreadnode:
438
451
  name: str | None = None,
439
452
  label: str | None = None,
440
453
  log_params: t.Sequence[str] | bool = False,
441
- log_inputs: t.Sequence[str] | bool = True,
442
- log_output: bool = True,
454
+ log_inputs: t.Sequence[str] | bool | Inherited = INHERITED,
455
+ log_output: bool | Inherited = INHERITED,
443
456
  tags: t.Sequence[str] | None = None,
444
457
  **attributes: t.Any,
445
458
  ) -> TaskDecorator:
@@ -621,6 +634,7 @@ class Dreadnode:
621
634
  tags: t.Sequence[str] | None = None,
622
635
  params: AnyDict | None = None,
623
636
  project: str | None = None,
637
+ autolog: bool = True,
624
638
  **attributes: t.Any,
625
639
  ) -> RunSpan:
626
640
  """
@@ -646,6 +660,7 @@ class Dreadnode:
646
660
  project: The project name to associate the run with. If not provided,
647
661
  the project passed to `configure()` will be used, or the
648
662
  run will be associated with a default project.
663
+ autolog: Whether to automatically log task inputs, outputs, and execution metrics if unspecified.
649
664
  **attributes: Additional attributes to attach to the run span.
650
665
  """
651
666
  if not self._initialized:
@@ -663,6 +678,7 @@ class Dreadnode:
663
678
  tags=tags,
664
679
  file_system=self._fs,
665
680
  prefix_path=self._fs_prefix,
681
+ autolog=autolog,
666
682
  )
667
683
 
668
684
  @handle_internal_errors()
@@ -759,6 +775,7 @@ class Dreadnode:
759
775
  origin: t.Any | None = None,
760
776
  timestamp: datetime | None = None,
761
777
  mode: MetricAggMode | None = None,
778
+ attributes: JsonDict | None = None,
762
779
  to: ToObject = "task-or-run",
763
780
  ) -> None:
764
781
  """
@@ -788,6 +805,7 @@ class Dreadnode:
788
805
  - avg: the average of all reported values for this metric
789
806
  - sum: the cumulative sum of all reported values for this metric
790
807
  - count: increment every time this metric is logged - disregard value
808
+ attributes: A dictionary of additional attributes to attach to the metric.
791
809
  to: The target object to log the metric to. Can be "task-or-run" or "run".
792
810
  Defaults to "task-or-run". If "task-or-run", the metric will be logged
793
811
  to the current task or run, whichever is the nearest ancestor.
@@ -842,6 +860,7 @@ class Dreadnode:
842
860
  origin: t.Any | None = None,
843
861
  timestamp: datetime | None = None,
844
862
  mode: MetricAggMode | None = None,
863
+ attributes: JsonDict | None = None,
845
864
  to: ToObject = "task-or-run",
846
865
  ) -> None:
847
866
  task = current_task_span.get()
@@ -854,7 +873,9 @@ class Dreadnode:
854
873
  metric = (
855
874
  value
856
875
  if isinstance(value, Metric)
857
- else Metric(float(value), step, timestamp or datetime.now(timezone.utc))
876
+ else Metric(
877
+ float(value), step, timestamp or datetime.now(timezone.utc), attributes or {}
878
+ )
858
879
  )
859
880
  target.log_metric(key, metric, origin=origin, mode=mode)
860
881
 
@@ -1,12 +1,15 @@
1
1
  import typing as t
2
2
  from dataclasses import dataclass
3
3
 
4
+ from dreadnode.types import JsonDict
5
+
4
6
 
5
7
  @dataclass
6
8
  class ObjectRef:
7
9
  name: str
8
10
  label: str
9
11
  hash: str
12
+ attributes: JsonDict
10
13
 
11
14
 
12
15
  @dataclass
@@ -9,6 +9,7 @@ from opentelemetry.trace import Tracer
9
9
 
10
10
  from dreadnode.metric import Scorer, ScorerCallable
11
11
  from dreadnode.tracing.span import TaskSpan, current_run_span
12
+ from dreadnode.types import INHERITED, Inherited
12
13
 
13
14
  P = t.ParamSpec("P")
14
15
  R = t.TypeVar("R")
@@ -114,9 +115,9 @@ class Task(t.Generic[P, R]):
114
115
 
115
116
  log_params: t.Sequence[str] | bool = False
116
117
  "Whether to log all, or specific, incoming arguments to the function as parameters."
117
- log_inputs: t.Sequence[str] | bool = True
118
+ log_inputs: t.Sequence[str] | bool | Inherited = INHERITED
118
119
  "Whether to log all, or specific, incoming arguments to the function as inputs."
119
- log_output: bool = True
120
+ log_output: bool | Inherited = INHERITED
120
121
  "Whether to automatically log the result of the function as an output."
121
122
 
122
123
  def __post_init__(self) -> None:
@@ -128,6 +129,25 @@ class Task(t.Generic[P, R]):
128
129
  self.__name__ = getattr(self.func, "__name__", self.name)
129
130
  self.__doc__ = getattr(self.func, "__doc__", None)
130
131
 
132
+ def __get__(self, obj: t.Any, objtype: t.Any) -> "Task[P, R]":
133
+ if obj is None:
134
+ return self
135
+
136
+ bound_func = self.func.__get__(obj, objtype)
137
+
138
+ return Task(
139
+ tracer=self.tracer,
140
+ name=self.name,
141
+ label=self.label,
142
+ attributes=self.attributes,
143
+ func=bound_func,
144
+ scorers=[scorer.clone() for scorer in self.scorers],
145
+ tags=self.tags.copy(),
146
+ log_params=self.log_params,
147
+ log_inputs=self.log_inputs,
148
+ log_output=self.log_output,
149
+ )
150
+
131
151
  def _bind_args(self, *args: P.args, **kwargs: P.kwargs) -> dict[str, t.Any]:
132
152
  signature = inspect.signature(self.func)
133
153
  bound_args = signature.bind(*args, **kwargs)
@@ -220,6 +240,9 @@ class Task(t.Generic[P, R]):
220
240
  if run is None or not run.is_recording:
221
241
  raise RuntimeError("Tasks must be executed within a run")
222
242
 
243
+ log_inputs = run.autolog if isinstance(self.log_inputs, Inherited) else self.log_inputs
244
+ log_output = run.autolog if isinstance(self.log_output, Inherited) else self.log_output
245
+
223
246
  bound_args = self._bind_args(*args, **kwargs)
224
247
 
225
248
  params_to_log = (
@@ -231,9 +254,9 @@ class Task(t.Generic[P, R]):
231
254
  )
232
255
  inputs_to_log = (
233
256
  bound_args
234
- if self.log_inputs is True
235
- else {k: v for k, v in bound_args.items() if k in self.log_inputs}
236
- if self.log_inputs is not False
257
+ if log_inputs is True
258
+ else {k: v for k, v in bound_args.items() if k in log_inputs}
259
+ if log_inputs is not False
237
260
  else {}
238
261
  )
239
262
 
@@ -246,13 +269,16 @@ class Task(t.Generic[P, R]):
246
269
  run_id=run.run_id,
247
270
  tracer=self.tracer,
248
271
  ) as span:
249
- span.run.log_metric(f"{self.label}.exec.count", 1, mode="count")
272
+ if run.autolog:
273
+ span.run.log_metric(
274
+ "count", 1, prefix=f"{self.label}.exec", mode="count", attributes={"auto": True}
275
+ )
250
276
 
251
277
  for name, value in params_to_log.items():
252
278
  span.log_param(name, value)
253
279
 
254
280
  input_object_hashes: list[str] = [
255
- span.log_input(name, value, label=f"{self.label}.input.{name}")
281
+ span.log_input(name, value, label=f"{self.label}.input.{name}", auto=True)
256
282
  for name, value in inputs_to_log.items()
257
283
  ]
258
284
 
@@ -261,17 +287,29 @@ class Task(t.Generic[P, R]):
261
287
  if inspect.isawaitable(output):
262
288
  output = await output
263
289
  except Exception:
264
- span.run.log_metric(f"{self.label}.exec.success_rate", 0, mode="avg")
290
+ if run.autolog:
291
+ span.run.log_metric(
292
+ "success_rate",
293
+ 0,
294
+ prefix=f"{self.label}.exec",
295
+ mode="avg",
296
+ attributes={"auto": True},
297
+ )
265
298
  raise
266
299
 
267
- span.run.log_metric(f"{self.label}.exec.success_rate", 1, mode="avg")
300
+ if run.autolog:
301
+ span.run.log_metric(
302
+ "success_rate",
303
+ 1,
304
+ prefix=f"{self.label}.exec",
305
+ mode="avg",
306
+ attributes={"auto": True},
307
+ )
268
308
  span.output = output
269
309
 
270
- if self.log_output:
310
+ if log_output:
271
311
  output_object_hash = span.log_output(
272
- "output",
273
- output,
274
- label=f"{self.label}.output",
312
+ "output", output, label=f"{self.label}.output", auto=True
275
313
  )
276
314
 
277
315
  # Link the output to the inputs
@@ -250,11 +250,15 @@ class RunSpan(Span):
250
250
  tracer: Tracer,
251
251
  file_system: AbstractFileSystem,
252
252
  prefix_path: str,
253
+ *,
253
254
  params: AnyDict | None = None,
254
255
  metrics: MetricDict | None = None,
255
256
  run_id: str | None = None,
256
257
  tags: t.Sequence[str] | None = None,
258
+ autolog: bool = True,
257
259
  ) -> None:
260
+ self.autolog = autolog
261
+
258
262
  self._params = params or {}
259
263
  self._metrics = metrics or {}
260
264
  self._objects: dict[str, Object] = {}
@@ -301,6 +305,7 @@ class RunSpan(Span):
301
305
  ) -> None:
302
306
  self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params)
303
307
  self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs, schema=False)
308
+ self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs, schema=False)
304
309
  self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics, schema=False)
305
310
  self.set_attribute(SPAN_ATTRIBUTE_OBJECTS, self._objects, schema=False)
306
311
  self.set_attribute(
@@ -485,9 +490,8 @@ class RunSpan(Span):
485
490
  value,
486
491
  label=label,
487
492
  event_name=EVENT_NAME_OBJECT_INPUT,
488
- **attributes,
489
493
  )
490
- self._inputs.append(ObjectRef(name, label=label, hash=hash_))
494
+ self._inputs.append(ObjectRef(name, label=label, hash=hash_, attributes=attributes))
491
495
 
492
496
  def log_artifact(
493
497
  self,
@@ -527,6 +531,8 @@ class RunSpan(Span):
527
531
  origin: t.Any | None = None,
528
532
  timestamp: datetime | None = None,
529
533
  mode: MetricAggMode | None = None,
534
+ prefix: str | None = None,
535
+ attributes: JsonDict | None = None,
530
536
  ) -> None: ...
531
537
 
532
538
  @t.overload
@@ -537,6 +543,7 @@ class RunSpan(Span):
537
543
  *,
538
544
  origin: t.Any | None = None,
539
545
  mode: MetricAggMode | None = None,
546
+ prefix: str | None = None,
540
547
  ) -> None: ...
541
548
 
542
549
  def log_metric(
@@ -548,13 +555,21 @@ class RunSpan(Span):
548
555
  origin: t.Any | None = None,
549
556
  timestamp: datetime | None = None,
550
557
  mode: MetricAggMode | None = None,
558
+ prefix: str | None = None,
559
+ attributes: JsonDict | None = None,
551
560
  ) -> None:
552
561
  metric = (
553
562
  value
554
563
  if isinstance(value, Metric)
555
- else Metric(float(value), step, timestamp or datetime.now(timezone.utc))
564
+ else Metric(
565
+ float(value), step, timestamp or datetime.now(timezone.utc), attributes or {}
566
+ )
556
567
  )
557
568
 
569
+ key = re.sub(r"[^\w/]+", "_", key.lower())
570
+ if prefix is not None:
571
+ key = f"{prefix}.{key}"
572
+
558
573
  if origin is not None:
559
574
  origin_hash = self.log_object(
560
575
  origin,
@@ -585,9 +600,8 @@ class RunSpan(Span):
585
600
  value,
586
601
  label=label,
587
602
  event_name=EVENT_NAME_OBJECT_OUTPUT,
588
- **attributes,
589
603
  )
590
- self._outputs.append(ObjectRef(name, label=label, hash=hash_))
604
+ self._outputs.append(ObjectRef(name, label=label, hash=hash_, attributes=attributes))
591
605
 
592
606
 
593
607
  class TaskSpan(Span, t.Generic[R]):
@@ -689,9 +703,8 @@ class TaskSpan(Span, t.Generic[R]):
689
703
  value,
690
704
  label=label,
691
705
  event_name=EVENT_NAME_OBJECT_OUTPUT,
692
- **attributes,
693
706
  )
694
- self._outputs.append(ObjectRef(name, label=label, hash=hash_))
707
+ self._outputs.append(ObjectRef(name, label=label, hash=hash_, attributes=attributes))
695
708
  return hash_
696
709
 
697
710
  @property
@@ -721,9 +734,8 @@ class TaskSpan(Span, t.Generic[R]):
721
734
  value,
722
735
  label=label,
723
736
  event_name=EVENT_NAME_OBJECT_INPUT,
724
- **attributes,
725
737
  )
726
- self._inputs.append(ObjectRef(name, label=label, hash=hash_))
738
+ self._inputs.append(ObjectRef(name, label=label, hash=hash_, attributes=attributes))
727
739
  return hash_
728
740
 
729
741
  @property
@@ -740,6 +752,7 @@ class TaskSpan(Span, t.Generic[R]):
740
752
  origin: t.Any | None = None,
741
753
  timestamp: datetime | None = None,
742
754
  mode: MetricAggMode | None = None,
755
+ attributes: JsonDict | None = None,
743
756
  ) -> None: ...
744
757
 
745
758
  @t.overload
@@ -761,13 +774,18 @@ class TaskSpan(Span, t.Generic[R]):
761
774
  origin: t.Any | None = None,
762
775
  timestamp: datetime | None = None,
763
776
  mode: MetricAggMode | None = None,
777
+ attributes: JsonDict | None = None,
764
778
  ) -> None:
765
779
  metric = (
766
780
  value
767
781
  if isinstance(value, Metric)
768
- else Metric(float(value), step, timestamp or datetime.now(timezone.utc))
782
+ else Metric(
783
+ float(value), step, timestamp or datetime.now(timezone.utc), attributes or {}
784
+ )
769
785
  )
770
786
 
787
+ key = re.sub(r"[^\w/]+", "_", key.lower())
788
+
771
789
  if origin is not None:
772
790
  origin_hash = self.run.log_object(
773
791
  origin,
@@ -786,7 +804,7 @@ class TaskSpan(Span, t.Generic[R]):
786
804
  #
787
805
  # Don't include `source` and `mode` as we handled it here.
788
806
  if (run := current_run_span.get()) is not None:
789
- run.log_metric(f"{self._label}.{key}", metric)
807
+ run.log_metric(key, metric, prefix=self._label)
790
808
 
791
809
  def get_average_metric_value(self, key: str | None = None) -> float:
792
810
  metrics = (
@@ -23,3 +23,11 @@ class Unset:
23
23
 
24
24
 
25
25
  UNSET: Unset = Unset()
26
+
27
+
28
+ class Inherited:
29
+ def __repr__(self) -> str:
30
+ return "Inherited"
31
+
32
+
33
+ INHERITED: Inherited = Inherited()
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "dreadnode"
3
- version = "1.0.0-rc.3"
3
+ version = "1.0.2"
4
4
  description = "Dreadnode SDK"
5
5
  requires-python = ">=3.10,<3.14"
6
6
 
7
7
  [tool.poetry]
8
8
  name = "dreadnode"
9
- version = "1.0.0-rc.3"
9
+ version = "1.0.2"
10
10
  description = "Dreadnode SDK"
11
11
  authors = ["Nick Landers <monoxgas@gmail.com>"]
12
12
  repository = "https://github.com/dreadnode/sdk"
@@ -18,7 +18,6 @@ pydantic = "^2.9.2"
18
18
  httpx = "^0.28.0"
19
19
  logfire = "^3.5.3"
20
20
  python-ulid = "^3.0.0"
21
- fast-depends = "^2.4.12"
22
21
  coolname = "^2.2.0"
23
22
  pandas = "^2.2.3"
24
23
  fsspec = {version = ">=2023.1.0,<=2025.3.0", extras = ["s3"]} # Pinned for datasets compatibility
@@ -31,7 +30,7 @@ training = ["transformers"]
31
30
  [tool.poetry.group.dev.dependencies]
32
31
  mypy = "^1.8.0"
33
32
  ruff = "^0.11.6"
34
- pre-commit = "^3.8.0"
33
+ pre-commit = "^4.0.0"
35
34
  pytest = "^8.3.3"
36
35
  pytest-asyncio = "^0.24.0"
37
36
  types-protobuf = "^5.29.1.20250208"
@@ -1,210 +0,0 @@
1
- import typing as t
2
- from datetime import datetime
3
- from uuid import UUID
4
-
5
- from pydantic import BaseModel, Field
6
- from ulid import ULID
7
-
8
- AnyDict = dict[str, t.Any]
9
-
10
- # User
11
-
12
-
13
- class UserAPIKey(BaseModel):
14
- key: str
15
-
16
-
17
- class UserResponse(BaseModel):
18
- id: UUID
19
- email_address: str
20
- username: str
21
- api_key: UserAPIKey
22
-
23
-
24
- # Strikes
25
-
26
- SpanStatus = t.Literal[
27
- "pending", # A pending span has been created
28
- "completed", # The span has been finished
29
- "failed", # The raised an exception
30
- ]
31
-
32
- ExportFormat = t.Literal["csv", "json", "jsonl", "parquet"]
33
- StatusFilter = t.Literal["all", "completed", "failed"]
34
- TimeAxisType = t.Literal["wall", "relative", "step"]
35
- TimeAggregationType = t.Literal["max", "min", "sum", "count"]
36
- MetricAggregationType = t.Literal[
37
- "avg",
38
- "median",
39
- "min",
40
- "max",
41
- "sum",
42
- "first",
43
- "last",
44
- "count",
45
- "std",
46
- "var",
47
- ]
48
-
49
-
50
- class SpanException(BaseModel):
51
- type: str
52
- message: str
53
- stacktrace: str
54
-
55
-
56
- class SpanEvent(BaseModel):
57
- timestamp: datetime
58
- name: str
59
- attributes: AnyDict
60
-
61
-
62
- class SpanLink(BaseModel):
63
- trace_id: str
64
- span_id: str
65
- attributes: AnyDict
66
-
67
-
68
- class TraceLog(BaseModel):
69
- timestamp: datetime
70
- body: str
71
- severity: str
72
- service: str | None
73
- trace_id: str | None
74
- span_id: str | None
75
- attributes: AnyDict
76
- container: str | None
77
-
78
-
79
- class TraceSpan(BaseModel):
80
- timestamp: datetime
81
- duration: int
82
- trace_id: str
83
- span_id: str
84
- parent_span_id: str | None
85
- service_name: str | None
86
- status: SpanStatus
87
- exception: SpanException | None
88
- name: str
89
- attributes: AnyDict
90
- resource_attributes: AnyDict
91
- events: list[SpanEvent]
92
- links: list[SpanLink]
93
-
94
-
95
- class Metric(BaseModel):
96
- value: float
97
- step: int
98
- timestamp: datetime
99
- attributes: AnyDict
100
-
101
-
102
- class ObjectRef(BaseModel):
103
- name: str
104
- label: str
105
- hash: str
106
-
107
-
108
- class ObjectUri(BaseModel):
109
- hash: str
110
- schema_hash: str
111
- uri: str
112
- size: int
113
- type: t.Literal["uri"]
114
-
115
-
116
- class ObjectVal(BaseModel):
117
- hash: str
118
- schema_hash: str
119
- value: t.Any
120
- type: t.Literal["val"]
121
-
122
-
123
- Object = ObjectUri | ObjectVal
124
-
125
-
126
- class V0Object(BaseModel):
127
- name: str
128
- label: str
129
- value: t.Any
130
-
131
-
132
- class Run(BaseModel):
133
- id: ULID
134
- name: str
135
- span_id: str
136
- trace_id: str
137
- timestamp: datetime
138
- duration: int
139
- status: SpanStatus
140
- exception: SpanException | None
141
- tags: set[str]
142
- params: AnyDict
143
- metrics: dict[str, list[Metric]]
144
- inputs: list[ObjectRef]
145
- outputs: list[ObjectRef]
146
- objects: dict[str, Object]
147
- object_schemas: AnyDict
148
- schema_: AnyDict = Field(alias="schema")
149
-
150
-
151
- class Task(BaseModel):
152
- name: str
153
- span_id: str
154
- trace_id: str
155
- parent_span_id: str | None
156
- parent_task_span_id: str | None
157
- timestamp: datetime
158
- duration: int
159
- status: SpanStatus
160
- exception: SpanException | None
161
- tags: set[str]
162
- params: AnyDict
163
- metrics: dict[str, list[Metric]]
164
- inputs: list[ObjectRef] | list[V0Object] # v0 compat
165
- outputs: list[ObjectRef] | list[V0Object] # v0 compat
166
- schema_: AnyDict = Field(alias="schema")
167
- attributes: AnyDict
168
- resource_attributes: AnyDict
169
- events: list[SpanEvent]
170
- links: list[SpanLink]
171
-
172
-
173
- class Project(BaseModel):
174
- id: UUID
175
- key: str
176
- name: str
177
- description: str | None
178
- created_at: datetime
179
- updated_at: datetime
180
- run_count: int
181
- last_run: Run | None
182
-
183
-
184
- # Derived types
185
-
186
-
187
- class TaskTree(BaseModel):
188
- task: Task
189
- children: list["TaskTree"] = []
190
-
191
-
192
- class SpanTree(BaseModel):
193
- """Tree representation of a trace span with its children"""
194
-
195
- span: Task | TraceSpan
196
- children: list["SpanTree"] = []
197
-
198
-
199
- # User data credentials
200
-
201
-
202
- class UserDataCredentials(BaseModel):
203
- access_key_id: str
204
- secret_access_key: str
205
- session_token: str
206
- expiration: datetime
207
- region: str
208
- bucket: str
209
- prefix: str
210
- endpoint: str | None
File without changes