dreadnode 1.0.3__tar.gz → 1.0.5__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.
- {dreadnode-1.0.3 → dreadnode-1.0.5}/PKG-INFO +1 -1
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/__init__.py +13 -3
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/api/client.py +0 -2
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/main.py +57 -28
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/metric.py +3 -2
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/tracing/span.py +39 -35
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/util.py +8 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/pyproject.toml +3 -3
- {dreadnode-1.0.3 → dreadnode-1.0.5}/README.md +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/api/__init__.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/api/models.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/api/util.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/artifact/__init__.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/artifact/merger.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/artifact/storage.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/artifact/tree_builder.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/constants.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/integrations/__init__.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/integrations/transformers.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/object.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/py.typed +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/serialization.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/task.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/tracing/__init__.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/tracing/constants.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/tracing/exporters.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/types.py +0 -0
- {dreadnode-1.0.3 → dreadnode-1.0.5}/dreadnode/version.py +0 -0
|
@@ -16,6 +16,7 @@ run = DEFAULT_INSTANCE.run
|
|
|
16
16
|
scorer = DEFAULT_INSTANCE.scorer
|
|
17
17
|
task_span = DEFAULT_INSTANCE.task_span
|
|
18
18
|
push_update = DEFAULT_INSTANCE.push_update
|
|
19
|
+
tag = DEFAULT_INSTANCE.tag
|
|
19
20
|
|
|
20
21
|
log_metric = DEFAULT_INSTANCE.log_metric
|
|
21
22
|
log_param = DEFAULT_INSTANCE.log_param
|
|
@@ -33,19 +34,28 @@ __all__ = [
|
|
|
33
34
|
"Metric",
|
|
34
35
|
"MetricDict",
|
|
35
36
|
"Object",
|
|
36
|
-
"Run",
|
|
37
37
|
"RunSpan",
|
|
38
|
-
"Score",
|
|
39
38
|
"Scorer",
|
|
40
39
|
"Span",
|
|
41
40
|
"Task",
|
|
42
41
|
"TaskSpan",
|
|
43
|
-
"
|
|
42
|
+
"api",
|
|
44
43
|
"configure",
|
|
44
|
+
"link_objects",
|
|
45
|
+
"log_artifact",
|
|
46
|
+
"log_input",
|
|
47
|
+
"log_inputs",
|
|
45
48
|
"log_metric",
|
|
49
|
+
"log_output",
|
|
46
50
|
"log_param",
|
|
51
|
+
"log_params",
|
|
52
|
+
"push_update",
|
|
47
53
|
"run",
|
|
54
|
+
"scorer",
|
|
48
55
|
"shutdown",
|
|
49
56
|
"span",
|
|
57
|
+
"tag",
|
|
50
58
|
"task",
|
|
59
|
+
"task_span",
|
|
60
|
+
"task_span",
|
|
51
61
|
]
|
|
@@ -112,8 +112,6 @@ class ApiClient:
|
|
|
112
112
|
"""Make a request to the API. Raise an exception for non-200 status codes."""
|
|
113
113
|
|
|
114
114
|
response = self._request(method, path, params, json_data)
|
|
115
|
-
if response.status_code == 401: # noqa: PLR2004
|
|
116
|
-
raise RuntimeError("Authentication failed, please check your API token.")
|
|
117
115
|
|
|
118
116
|
try:
|
|
119
117
|
response.raise_for_status()
|
|
@@ -2,7 +2,6 @@ import contextlib
|
|
|
2
2
|
import inspect
|
|
3
3
|
import os
|
|
4
4
|
import random
|
|
5
|
-
import re
|
|
6
5
|
import typing as t
|
|
7
6
|
from dataclasses import dataclass
|
|
8
7
|
from datetime import datetime, timezone
|
|
@@ -53,7 +52,7 @@ from dreadnode.types import (
|
|
|
53
52
|
JsonDict,
|
|
54
53
|
JsonValue,
|
|
55
54
|
)
|
|
56
|
-
from dreadnode.util import handle_internal_errors
|
|
55
|
+
from dreadnode.util import clean_str, handle_internal_errors
|
|
57
56
|
from dreadnode.version import VERSION
|
|
58
57
|
|
|
59
58
|
if t.TYPE_CHECKING:
|
|
@@ -104,7 +103,7 @@ class Dreadnode:
|
|
|
104
103
|
service_name: str | None = None,
|
|
105
104
|
service_version: str | None = None,
|
|
106
105
|
console: logfire.ConsoleOptions | t.Literal[False, True] = True,
|
|
107
|
-
send_to_logfire: bool | t.Literal["if-token-present"] =
|
|
106
|
+
send_to_logfire: bool | t.Literal["if-token-present"] = False,
|
|
108
107
|
otel_scope: str = "dreadnode",
|
|
109
108
|
) -> None:
|
|
110
109
|
self.server = server
|
|
@@ -137,7 +136,7 @@ class Dreadnode:
|
|
|
137
136
|
service_name: str | None = None,
|
|
138
137
|
service_version: str | None = None,
|
|
139
138
|
console: logfire.ConsoleOptions | t.Literal[False, True] = True,
|
|
140
|
-
send_to_logfire: bool | t.Literal["if-token-present"] =
|
|
139
|
+
send_to_logfire: bool | t.Literal["if-token-present"] = False,
|
|
141
140
|
otel_scope: str = "dreadnode",
|
|
142
141
|
) -> None:
|
|
143
142
|
"""
|
|
@@ -198,8 +197,8 @@ class Dreadnode:
|
|
|
198
197
|
span_processors: list[SpanProcessor] = []
|
|
199
198
|
metric_readers: list[MetricReader] = []
|
|
200
199
|
|
|
201
|
-
self.server = self.server or DEFAULT_SERVER_URL
|
|
202
|
-
if self.server
|
|
200
|
+
self.server = self.server or (DEFAULT_SERVER_URL if self.token else None)
|
|
201
|
+
if not (self.server and self.token and self.local_dir):
|
|
203
202
|
warn_at_user_stacklevel(
|
|
204
203
|
"Your current configuration won't persist run data anywhere. "
|
|
205
204
|
"Use `dreadnode.init(server=..., token=...)`, `dreadnode.init(local_dir=...)`, "
|
|
@@ -207,17 +206,7 @@ class Dreadnode:
|
|
|
207
206
|
category=DreadnodeConfigWarning,
|
|
208
207
|
)
|
|
209
208
|
|
|
210
|
-
if self.
|
|
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
|
-
|
|
220
|
-
if self.local_dir is not False:
|
|
209
|
+
if self.local_dir:
|
|
221
210
|
config = FileExportConfig(
|
|
222
211
|
base_path=self.local_dir,
|
|
223
212
|
prefix=self.project + "-" if self.project else "",
|
|
@@ -225,14 +214,23 @@ class Dreadnode:
|
|
|
225
214
|
span_processors.append(BatchSpanProcessor(FileSpanExporter(config)))
|
|
226
215
|
metric_readers.append(FileMetricReader(config))
|
|
227
216
|
|
|
228
|
-
if self.token
|
|
229
|
-
self._api = ApiClient(self.server, self.token)
|
|
230
|
-
|
|
217
|
+
if self.token and self.server:
|
|
231
218
|
try:
|
|
219
|
+
parsed_url = urlparse(self.server)
|
|
220
|
+
if not parsed_url.scheme:
|
|
221
|
+
netloc = parsed_url.path.split("/")[0]
|
|
222
|
+
path = "/".join(parsed_url.path.split("/")[1:])
|
|
223
|
+
parsed_new = parsed_url._replace(
|
|
224
|
+
scheme="https", netloc=netloc, path=f"/{path}" if path else ""
|
|
225
|
+
)
|
|
226
|
+
self.server = urlunparse(parsed_new)
|
|
227
|
+
|
|
228
|
+
self._api = ApiClient(self.server, self.token)
|
|
229
|
+
|
|
232
230
|
self._api.list_projects()
|
|
233
231
|
except Exception as e:
|
|
234
232
|
raise RuntimeError(
|
|
235
|
-
"Failed to
|
|
233
|
+
"Failed to connect to the Dreadnode server.",
|
|
236
234
|
) from e
|
|
237
235
|
|
|
238
236
|
headers = {"User-Agent": f"dreadnode/{VERSION}", "X-Api-Key": self.token}
|
|
@@ -503,7 +501,7 @@ class Dreadnode:
|
|
|
503
501
|
_label = label or func_name
|
|
504
502
|
|
|
505
503
|
# conform our label for sanity
|
|
506
|
-
_label =
|
|
504
|
+
_label = clean_str(_label)
|
|
507
505
|
|
|
508
506
|
_attributes = attributes or {}
|
|
509
507
|
_attributes["code.function"] = func_name
|
|
@@ -570,7 +568,7 @@ class Dreadnode:
|
|
|
570
568
|
if (run := current_run_span.get()) is None:
|
|
571
569
|
raise RuntimeError("Task spans must be created within a run")
|
|
572
570
|
|
|
573
|
-
label = label or
|
|
571
|
+
label = label or clean_str(name)
|
|
574
572
|
return TaskSpan(
|
|
575
573
|
name=name,
|
|
576
574
|
label=label,
|
|
@@ -681,6 +679,31 @@ class Dreadnode:
|
|
|
681
679
|
autolog=autolog,
|
|
682
680
|
)
|
|
683
681
|
|
|
682
|
+
def tag(self, *tag: str, to: ToObject = "task-or-run") -> None:
|
|
683
|
+
"""
|
|
684
|
+
Add one or many tags to the current task or run.
|
|
685
|
+
|
|
686
|
+
Example:
|
|
687
|
+
```
|
|
688
|
+
with dreadnode.run("my_run") as run:
|
|
689
|
+
run.tag("my_tag")
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Args:
|
|
693
|
+
tag: The tag to attach to the task or run.
|
|
694
|
+
to: The target object to log the tag to. Can be "task-or-run" or "run".
|
|
695
|
+
Defaults to "task-or-run". If "task-or-run", the tag will be logged
|
|
696
|
+
to the current task or run, whichever is the nearest ancestor.
|
|
697
|
+
"""
|
|
698
|
+
task = current_task_span.get()
|
|
699
|
+
run = current_run_span.get()
|
|
700
|
+
|
|
701
|
+
target = (task or run) if to == "task-or-run" else run
|
|
702
|
+
if target is None:
|
|
703
|
+
raise RuntimeError("Tagging must be done within a run")
|
|
704
|
+
|
|
705
|
+
target.add_tags(tag)
|
|
706
|
+
|
|
684
707
|
@handle_internal_errors()
|
|
685
708
|
def push_update(self) -> None:
|
|
686
709
|
"""
|
|
@@ -777,7 +800,7 @@ class Dreadnode:
|
|
|
777
800
|
mode: MetricAggMode | None = None,
|
|
778
801
|
attributes: JsonDict | None = None,
|
|
779
802
|
to: ToObject = "task-or-run",
|
|
780
|
-
) ->
|
|
803
|
+
) -> Metric:
|
|
781
804
|
"""
|
|
782
805
|
Log a single metric to the current task or run.
|
|
783
806
|
|
|
@@ -809,6 +832,9 @@ class Dreadnode:
|
|
|
809
832
|
to: The target object to log the metric to. Can be "task-or-run" or "run".
|
|
810
833
|
Defaults to "task-or-run". If "task-or-run", the metric will be logged
|
|
811
834
|
to the current task or run, whichever is the nearest ancestor.
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
The logged metric object.
|
|
812
838
|
"""
|
|
813
839
|
|
|
814
840
|
@t.overload
|
|
@@ -820,7 +846,7 @@ class Dreadnode:
|
|
|
820
846
|
origin: t.Any | None = None,
|
|
821
847
|
mode: MetricAggMode | None = None,
|
|
822
848
|
to: ToObject = "task-or-run",
|
|
823
|
-
) ->
|
|
849
|
+
) -> Metric:
|
|
824
850
|
"""
|
|
825
851
|
Log a single metric to the current task or run.
|
|
826
852
|
|
|
@@ -848,6 +874,9 @@ class Dreadnode:
|
|
|
848
874
|
to: The target object to log the metric to. Can be "task-or-run" or "run".
|
|
849
875
|
Defaults to "task-or-run". If "task-or-run", the metric will be logged
|
|
850
876
|
to the current task or run, whichever is the nearest ancestor.
|
|
877
|
+
|
|
878
|
+
Returns:
|
|
879
|
+
The logged metric object.
|
|
851
880
|
"""
|
|
852
881
|
|
|
853
882
|
@handle_internal_errors()
|
|
@@ -862,7 +891,7 @@ class Dreadnode:
|
|
|
862
891
|
mode: MetricAggMode | None = None,
|
|
863
892
|
attributes: JsonDict | None = None,
|
|
864
893
|
to: ToObject = "task-or-run",
|
|
865
|
-
) ->
|
|
894
|
+
) -> Metric:
|
|
866
895
|
task = current_task_span.get()
|
|
867
896
|
run = current_run_span.get()
|
|
868
897
|
|
|
@@ -877,7 +906,7 @@ class Dreadnode:
|
|
|
877
906
|
float(value), step, timestamp or datetime.now(timezone.utc), attributes or {}
|
|
878
907
|
)
|
|
879
908
|
)
|
|
880
|
-
target.log_metric(key, metric, origin=origin, mode=mode)
|
|
909
|
+
return target.log_metric(key, metric, origin=origin, mode=mode)
|
|
881
910
|
|
|
882
911
|
@handle_internal_errors()
|
|
883
912
|
def log_artifact(
|
|
@@ -926,7 +955,7 @@ class Dreadnode:
|
|
|
926
955
|
def log_input(
|
|
927
956
|
self,
|
|
928
957
|
name: str,
|
|
929
|
-
value:
|
|
958
|
+
value: t.Any,
|
|
930
959
|
*,
|
|
931
960
|
label: str | None = None,
|
|
932
961
|
to: ToObject = "task-or-run",
|
|
@@ -68,7 +68,7 @@ class Metric:
|
|
|
68
68
|
This will modify the metric in place.
|
|
69
69
|
|
|
70
70
|
Args:
|
|
71
|
-
mode: The mode to apply. One of "sum", "min", "max", or "
|
|
71
|
+
mode: The mode to apply. One of "sum", "min", "max", or "count".
|
|
72
72
|
others: A list of other metrics to apply the mode to.
|
|
73
73
|
|
|
74
74
|
Returns:
|
|
@@ -87,7 +87,8 @@ class Metric:
|
|
|
87
87
|
prior_values = [m.value for m in sorted(others, key=lambda m: m.timestamp)]
|
|
88
88
|
|
|
89
89
|
if mode == "sum":
|
|
90
|
-
|
|
90
|
+
# Take the max of the priors because they might already be summed
|
|
91
|
+
self.value += max(prior_values) if prior_values else 0
|
|
91
92
|
elif mode == "min":
|
|
92
93
|
self.value = min([self.value, *prior_values])
|
|
93
94
|
elif mode == "max":
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import re
|
|
3
2
|
import types
|
|
4
3
|
import typing as t
|
|
5
4
|
from contextvars import ContextVar, Token
|
|
@@ -32,6 +31,7 @@ from dreadnode.metric import Metric, MetricAggMode, MetricDict
|
|
|
32
31
|
from dreadnode.object import Object, ObjectRef, ObjectUri, ObjectVal
|
|
33
32
|
from dreadnode.serialization import Serialized, serialize
|
|
34
33
|
from dreadnode.types import UNSET, AnyDict, JsonDict, JsonValue, Unset
|
|
34
|
+
from dreadnode.util import clean_str
|
|
35
35
|
from dreadnode.version import VERSION
|
|
36
36
|
|
|
37
37
|
from .constants import (
|
|
@@ -92,11 +92,16 @@ class Span(ReadableSpan):
|
|
|
92
92
|
) -> None:
|
|
93
93
|
self._label = label or ""
|
|
94
94
|
self._span_name = name
|
|
95
|
+
|
|
96
|
+
tags = [tags] if isinstance(tags, str) else list(tags or [])
|
|
97
|
+
tags = [clean_str(t) for t in tags]
|
|
98
|
+
self.tags: tuple[str, ...] = uniquify_sequence(tags)
|
|
99
|
+
|
|
95
100
|
self._pre_attributes = {
|
|
96
101
|
SPAN_ATTRIBUTE_VERSION: VERSION,
|
|
97
102
|
SPAN_ATTRIBUTE_TYPE: type,
|
|
98
103
|
SPAN_ATTRIBUTE_LABEL: self._label,
|
|
99
|
-
SPAN_ATTRIBUTE_TAGS_:
|
|
104
|
+
SPAN_ATTRIBUTE_TAGS_: self.tags,
|
|
100
105
|
**attributes,
|
|
101
106
|
}
|
|
102
107
|
self._tracer = tracer
|
|
@@ -145,6 +150,8 @@ class Span(ReadableSpan):
|
|
|
145
150
|
SPAN_ATTRIBUTE_SCHEMA,
|
|
146
151
|
attributes_json_schema(self._schema) if self._schema else r"{}",
|
|
147
152
|
)
|
|
153
|
+
self._span.set_attribute(SPAN_ATTRIBUTE_TAGS_, self.tags)
|
|
154
|
+
|
|
148
155
|
self._span.__exit__(exc_type, exc_value, traceback)
|
|
149
156
|
|
|
150
157
|
OPEN_SPANS.discard(self._span) # type: ignore [arg-type]
|
|
@@ -167,13 +174,14 @@ class Span(ReadableSpan):
|
|
|
167
174
|
return False
|
|
168
175
|
return self._span.is_recording()
|
|
169
176
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
def set_tags(self, tags: t.Sequence[str]) -> None:
|
|
178
|
+
tags = [tags] if isinstance(tags, str) else list(tags)
|
|
179
|
+
tags = [clean_str(t) for t in tags]
|
|
180
|
+
self.tags = uniquify_sequence(tags)
|
|
173
181
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.
|
|
182
|
+
def add_tags(self, tags: t.Sequence[str]) -> None:
|
|
183
|
+
tags = [tags] if isinstance(tags, str) else list(tags)
|
|
184
|
+
self.set_tags([*self.tags, *tags])
|
|
177
185
|
|
|
178
186
|
def set_attribute(
|
|
179
187
|
self,
|
|
@@ -485,7 +493,7 @@ class RunSpan(Span):
|
|
|
485
493
|
label: str | None = None,
|
|
486
494
|
**attributes: JsonValue,
|
|
487
495
|
) -> None:
|
|
488
|
-
label = label or
|
|
496
|
+
label = label or clean_str(name)
|
|
489
497
|
hash_ = self.log_object(
|
|
490
498
|
value,
|
|
491
499
|
label=label,
|
|
@@ -533,7 +541,7 @@ class RunSpan(Span):
|
|
|
533
541
|
mode: MetricAggMode | None = None,
|
|
534
542
|
prefix: str | None = None,
|
|
535
543
|
attributes: JsonDict | None = None,
|
|
536
|
-
) ->
|
|
544
|
+
) -> Metric: ...
|
|
537
545
|
|
|
538
546
|
@t.overload
|
|
539
547
|
def log_metric(
|
|
@@ -544,7 +552,7 @@ class RunSpan(Span):
|
|
|
544
552
|
origin: t.Any | None = None,
|
|
545
553
|
mode: MetricAggMode | None = None,
|
|
546
554
|
prefix: str | None = None,
|
|
547
|
-
) ->
|
|
555
|
+
) -> Metric: ...
|
|
548
556
|
|
|
549
557
|
def log_metric(
|
|
550
558
|
self,
|
|
@@ -557,7 +565,7 @@ class RunSpan(Span):
|
|
|
557
565
|
mode: MetricAggMode | None = None,
|
|
558
566
|
prefix: str | None = None,
|
|
559
567
|
attributes: JsonDict | None = None,
|
|
560
|
-
) ->
|
|
568
|
+
) -> Metric:
|
|
561
569
|
metric = (
|
|
562
570
|
value
|
|
563
571
|
if isinstance(value, Metric)
|
|
@@ -566,7 +574,7 @@ class RunSpan(Span):
|
|
|
566
574
|
)
|
|
567
575
|
)
|
|
568
576
|
|
|
569
|
-
key =
|
|
577
|
+
key = clean_str(key)
|
|
570
578
|
if prefix is not None:
|
|
571
579
|
key = f"{prefix}.{key}"
|
|
572
580
|
|
|
@@ -583,6 +591,8 @@ class RunSpan(Span):
|
|
|
583
591
|
metric = metric.apply_mode(mode, metrics)
|
|
584
592
|
metrics.append(metric)
|
|
585
593
|
|
|
594
|
+
return metric
|
|
595
|
+
|
|
586
596
|
@property
|
|
587
597
|
def outputs(self) -> AnyDict:
|
|
588
598
|
return {ref.name: self.get_object(ref.hash) for ref in self._outputs}
|
|
@@ -595,7 +605,7 @@ class RunSpan(Span):
|
|
|
595
605
|
label: str | None = None,
|
|
596
606
|
**attributes: JsonValue,
|
|
597
607
|
) -> None:
|
|
598
|
-
label = label or
|
|
608
|
+
label = label or clean_str(name)
|
|
599
609
|
hash_ = self.log_object(
|
|
600
610
|
value,
|
|
601
611
|
label=label,
|
|
@@ -698,7 +708,7 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
698
708
|
label: str | None = None,
|
|
699
709
|
**attributes: JsonValue,
|
|
700
710
|
) -> str:
|
|
701
|
-
label = label or
|
|
711
|
+
label = label or clean_str(name)
|
|
702
712
|
hash_ = self.run.log_object(
|
|
703
713
|
value,
|
|
704
714
|
label=label,
|
|
@@ -729,7 +739,7 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
729
739
|
label: str | None = None,
|
|
730
740
|
**attributes: JsonValue,
|
|
731
741
|
) -> str:
|
|
732
|
-
label = label or
|
|
742
|
+
label = label or clean_str(name)
|
|
733
743
|
hash_ = self.run.log_object(
|
|
734
744
|
value,
|
|
735
745
|
label=label,
|
|
@@ -753,7 +763,7 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
753
763
|
timestamp: datetime | None = None,
|
|
754
764
|
mode: MetricAggMode | None = None,
|
|
755
765
|
attributes: JsonDict | None = None,
|
|
756
|
-
) ->
|
|
766
|
+
) -> Metric: ...
|
|
757
767
|
|
|
758
768
|
@t.overload
|
|
759
769
|
def log_metric(
|
|
@@ -763,7 +773,7 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
763
773
|
*,
|
|
764
774
|
origin: t.Any | None = None,
|
|
765
775
|
mode: MetricAggMode | None = None,
|
|
766
|
-
) ->
|
|
776
|
+
) -> Metric: ...
|
|
767
777
|
|
|
768
778
|
def log_metric(
|
|
769
779
|
self,
|
|
@@ -775,7 +785,7 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
775
785
|
timestamp: datetime | None = None,
|
|
776
786
|
mode: MetricAggMode | None = None,
|
|
777
787
|
attributes: JsonDict | None = None,
|
|
778
|
-
) ->
|
|
788
|
+
) -> Metric:
|
|
779
789
|
metric = (
|
|
780
790
|
value
|
|
781
791
|
if isinstance(value, Metric)
|
|
@@ -784,27 +794,21 @@ class TaskSpan(Span, t.Generic[R]):
|
|
|
784
794
|
)
|
|
785
795
|
)
|
|
786
796
|
|
|
787
|
-
key =
|
|
788
|
-
|
|
789
|
-
if origin is not None:
|
|
790
|
-
origin_hash = self.run.log_object(
|
|
791
|
-
origin,
|
|
792
|
-
label=key,
|
|
793
|
-
event_name=EVENT_NAME_OBJECT_METRIC,
|
|
794
|
-
)
|
|
795
|
-
metric.attributes[METRIC_ATTRIBUTE_SOURCE_HASH] = origin_hash
|
|
796
|
-
|
|
797
|
-
metrics = self._metrics.setdefault(key, [])
|
|
798
|
-
if mode is not None:
|
|
799
|
-
metric = metric.apply_mode(mode, metrics)
|
|
800
|
-
metrics.append(metric)
|
|
797
|
+
key = clean_str(key)
|
|
801
798
|
|
|
802
799
|
# For every metric we log, also log it to the run
|
|
803
800
|
# with our `label` as a prefix.
|
|
804
801
|
#
|
|
805
|
-
#
|
|
802
|
+
# Let the run handle the origin and mode aggregation
|
|
803
|
+
# for us as we don't have access to the other times
|
|
804
|
+
# this task-metric was logged here.
|
|
805
|
+
|
|
806
806
|
if (run := current_run_span.get()) is not None:
|
|
807
|
-
run.log_metric(key, metric, prefix=self._label)
|
|
807
|
+
metric = run.log_metric(key, metric, prefix=self._label, origin=origin, mode=mode)
|
|
808
|
+
|
|
809
|
+
self._metrics.setdefault(key, []).append(metric)
|
|
810
|
+
|
|
811
|
+
return metric
|
|
808
812
|
|
|
809
813
|
def get_average_metric_value(self, key: str | None = None) -> float:
|
|
810
814
|
metrics = (
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import inspect
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
import re
|
|
7
8
|
import sys
|
|
8
9
|
import typing as t
|
|
9
10
|
from contextlib import contextmanager
|
|
@@ -27,6 +28,13 @@ logger = logging.getLogger("dreadnode")
|
|
|
27
28
|
add_non_user_code_prefix(Path(dreadnode.__file__).parent)
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def clean_str(s: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Clean a string by replacing all non-alphanumeric characters with underscores.
|
|
34
|
+
"""
|
|
35
|
+
return re.sub(r"[^\w/]+", "_", s.lower())
|
|
36
|
+
|
|
37
|
+
|
|
30
38
|
def safe_repr(obj: t.Any) -> str:
|
|
31
39
|
"""
|
|
32
40
|
Return some kind of non-empty string representation of an object, catching exceptions.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dreadnode"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.5"
|
|
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.
|
|
9
|
+
version = "1.0.5"
|
|
10
10
|
description = "Dreadnode SDK"
|
|
11
11
|
authors = ["Nick Landers <monoxgas@gmail.com>"]
|
|
12
12
|
repository = "https://github.com/dreadnode/sdk"
|
|
@@ -32,7 +32,7 @@ mypy = "^1.8.0"
|
|
|
32
32
|
ruff = "^0.11.6"
|
|
33
33
|
pre-commit = "^4.0.0"
|
|
34
34
|
pytest = "^8.3.3"
|
|
35
|
-
pytest-asyncio = "^0.
|
|
35
|
+
pytest-asyncio = "^0.26.0"
|
|
36
36
|
types-protobuf = "^5.29.1.20250208"
|
|
37
37
|
pandas-stubs = "^2.2.3.250308"
|
|
38
38
|
types-requests = "^2.32.0.20250306"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|