dreadnode 1.0.0rc0__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.
- dreadnode/__init__.py +51 -0
- dreadnode/api/__init__.py +0 -0
- dreadnode/api/client.py +249 -0
- dreadnode/api/models.py +210 -0
- dreadnode/artifact/__init__.py +0 -0
- dreadnode/artifact/merger.py +599 -0
- dreadnode/artifact/storage.py +126 -0
- dreadnode/artifact/tree_builder.py +455 -0
- dreadnode/constants.py +16 -0
- dreadnode/integrations/__init__.py +0 -0
- dreadnode/integrations/transformers.py +183 -0
- dreadnode/main.py +1042 -0
- dreadnode/metric.py +225 -0
- dreadnode/object.py +29 -0
- dreadnode/py.typed +0 -0
- dreadnode/serialization.py +731 -0
- dreadnode/task.py +447 -0
- dreadnode/tracing/__init__.py +0 -0
- dreadnode/tracing/constants.py +35 -0
- dreadnode/tracing/exporters.py +157 -0
- dreadnode/tracing/span.py +811 -0
- dreadnode/types.py +25 -0
- dreadnode/util.py +150 -0
- dreadnode/version.py +3 -0
- dreadnode-1.0.0rc0.dist-info/METADATA +122 -0
- dreadnode-1.0.0rc0.dist-info/RECORD +27 -0
- dreadnode-1.0.0rc0.dist-info/WHEEL +4 -0
dreadnode/metric.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import typing as t
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
from logfire._internal.utils import safe_repr
|
|
7
|
+
from opentelemetry.trace import Tracer
|
|
8
|
+
|
|
9
|
+
from dreadnode.types import JsonDict, JsonValue
|
|
10
|
+
|
|
11
|
+
T = t.TypeVar("T")
|
|
12
|
+
|
|
13
|
+
MetricMode = t.Literal["direct", "avg", "sum", "min", "max", "count"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Metric:
|
|
18
|
+
"""
|
|
19
|
+
Any reported value regarding the state of a run, task, and optionally object (input/output).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
value: float
|
|
23
|
+
"The value of the metric, e.g. 0.5, 1.0, 2.0, etc."
|
|
24
|
+
step: int = 0
|
|
25
|
+
"An step value to indicate when this metric was reported."
|
|
26
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
27
|
+
"The timestamp when the metric was reported."
|
|
28
|
+
attributes: JsonDict = field(default_factory=dict)
|
|
29
|
+
"A dictionary of attributes to attach to the metric."
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_many(
|
|
33
|
+
cls,
|
|
34
|
+
values: t.Sequence[tuple[str, float, float]],
|
|
35
|
+
step: int = 0,
|
|
36
|
+
**attributes: JsonValue,
|
|
37
|
+
) -> "Metric":
|
|
38
|
+
"""
|
|
39
|
+
Create a composite metric from individual values and weights.
|
|
40
|
+
|
|
41
|
+
This is useful for creating a metric that is the weighted average of multiple values.
|
|
42
|
+
The values should be a sequence of tuples, where each tuple contains the name of the metric,
|
|
43
|
+
the value of the metric, and the weight of the metric.
|
|
44
|
+
|
|
45
|
+
The individual values will be reported in the attributes of the metric.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
values: A sequence of tuples containing the name, value, and weight of each metric.
|
|
49
|
+
step: The step value to attach to the metric.
|
|
50
|
+
**attributes: Additional attributes to attach to the metric.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A composite Metric
|
|
54
|
+
"""
|
|
55
|
+
total = sum(value * weight for _, value, weight in values)
|
|
56
|
+
weight = sum(weight for _, _, weight in values)
|
|
57
|
+
score_attributes = {name: value for name, value, _ in values}
|
|
58
|
+
return cls(value=total / weight, step=step, attributes={**attributes, **score_attributes})
|
|
59
|
+
|
|
60
|
+
def apply_mode(self, mode: MetricMode, others: "list[Metric]") -> "Metric":
|
|
61
|
+
"""
|
|
62
|
+
Apply an aggregation mode to the metric.
|
|
63
|
+
This will modify the metric in place.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
mode: The mode to apply. One of "sum", "min", "max", or "inc".
|
|
67
|
+
others: A list of other metrics to apply the mode to.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
self
|
|
71
|
+
"""
|
|
72
|
+
previous_mode = next((m.attributes.get("mode") for m in others), mode) or "direct"
|
|
73
|
+
if mode != previous_mode:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Cannot mix metric modes {mode} != {previous_mode}",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if mode == "direct":
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
self.attributes["original"] = self.value
|
|
82
|
+
self.attributes["mode"] = mode
|
|
83
|
+
|
|
84
|
+
prior_values = [m.value for m in sorted(others, key=lambda m: m.timestamp)]
|
|
85
|
+
|
|
86
|
+
if mode == "sum":
|
|
87
|
+
self.value += max(prior_values)
|
|
88
|
+
elif mode == "min":
|
|
89
|
+
self.value = min([self.value, *prior_values])
|
|
90
|
+
elif mode == "max":
|
|
91
|
+
self.value = max([self.value, *prior_values])
|
|
92
|
+
elif mode == "count":
|
|
93
|
+
self.value = len(others) + 1
|
|
94
|
+
elif mode == "avg" and prior_values:
|
|
95
|
+
current_avg = prior_values[-1]
|
|
96
|
+
self.value = current_avg + (self.value - current_avg) / (len(prior_values) + 1)
|
|
97
|
+
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
MetricDict = dict[str, list[Metric]]
|
|
102
|
+
|
|
103
|
+
ScorerResult = float | int | bool | Metric
|
|
104
|
+
ScorerCallable = t.Callable[[T], t.Awaitable[ScorerResult]] | t.Callable[[T], ScorerResult]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class Scorer(t.Generic[T]):
|
|
109
|
+
tracer: Tracer
|
|
110
|
+
|
|
111
|
+
name: str
|
|
112
|
+
"The name of the scorer, used for reporting metrics."
|
|
113
|
+
tags: t.Sequence[str]
|
|
114
|
+
"A list of tags to attach to the metric."
|
|
115
|
+
attributes: dict[str, t.Any]
|
|
116
|
+
"A dictionary of attributes to attach to the metric."
|
|
117
|
+
func: ScorerCallable[T]
|
|
118
|
+
"The function to call to get the metric."
|
|
119
|
+
step: int = 0
|
|
120
|
+
"The step value to attach to metrics produced by this Scorer."
|
|
121
|
+
auto_increment_step: bool = False
|
|
122
|
+
"Whether to automatically increment the step for each time this scorer is called."
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_callable(
|
|
126
|
+
cls,
|
|
127
|
+
tracer: Tracer,
|
|
128
|
+
func: "ScorerCallable[T] | Scorer[T]",
|
|
129
|
+
*,
|
|
130
|
+
name: str | None = None,
|
|
131
|
+
tags: t.Sequence[str] | None = None,
|
|
132
|
+
**attributes: t.Any,
|
|
133
|
+
) -> "Scorer[T]":
|
|
134
|
+
"""
|
|
135
|
+
Create a scorer from a callable function.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
tracer: The tracer to use for reporting metrics.
|
|
139
|
+
func: The function to call to get the metric.
|
|
140
|
+
name: The name of the scorer, used for reporting metrics.
|
|
141
|
+
tags: A list of tags to attach to the metric.
|
|
142
|
+
**attributes: A dictionary of attributes to attach to the metric.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
A Scorer object.
|
|
146
|
+
"""
|
|
147
|
+
if isinstance(func, Scorer):
|
|
148
|
+
if name is not None or attributes is not None:
|
|
149
|
+
func = func.clone()
|
|
150
|
+
func.name = name or func.name
|
|
151
|
+
func.attributes.update(attributes or {})
|
|
152
|
+
return func
|
|
153
|
+
|
|
154
|
+
func = inspect.unwrap(func)
|
|
155
|
+
func_name = getattr(
|
|
156
|
+
func,
|
|
157
|
+
"__qualname__",
|
|
158
|
+
getattr(func, "__name__", safe_repr(func)),
|
|
159
|
+
)
|
|
160
|
+
name = name or func_name
|
|
161
|
+
return cls(
|
|
162
|
+
tracer=tracer,
|
|
163
|
+
name=name,
|
|
164
|
+
tags=tags or [],
|
|
165
|
+
attributes=attributes or {},
|
|
166
|
+
func=func,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def __post_init__(self) -> None:
|
|
170
|
+
self.__signature__ = inspect.signature(self.func)
|
|
171
|
+
self.__name__ = self.name
|
|
172
|
+
|
|
173
|
+
def clone(self) -> "Scorer[T]":
|
|
174
|
+
"""
|
|
175
|
+
Clone the scorer.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
A new Scorer.
|
|
179
|
+
"""
|
|
180
|
+
return Scorer(
|
|
181
|
+
tracer=self.tracer,
|
|
182
|
+
name=self.name,
|
|
183
|
+
tags=self.tags,
|
|
184
|
+
attributes=self.attributes,
|
|
185
|
+
func=self.func,
|
|
186
|
+
step=self.step,
|
|
187
|
+
auto_increment_step=self.auto_increment_step,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def __call__(self, object: T) -> Metric:
|
|
191
|
+
"""
|
|
192
|
+
Execute the scorer and return the metric.
|
|
193
|
+
|
|
194
|
+
Any output value will be converted to a Metric object.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
object: The object to score.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
A Metric object.
|
|
201
|
+
"""
|
|
202
|
+
from dreadnode.tracing.span import Span
|
|
203
|
+
|
|
204
|
+
with Span(
|
|
205
|
+
name=self.name,
|
|
206
|
+
tags=self.tags,
|
|
207
|
+
attributes=self.attributes,
|
|
208
|
+
tracer=self.tracer,
|
|
209
|
+
):
|
|
210
|
+
metric = self.func(object)
|
|
211
|
+
if inspect.isawaitable(metric):
|
|
212
|
+
metric = await metric
|
|
213
|
+
|
|
214
|
+
if not isinstance(metric, Metric):
|
|
215
|
+
metric = Metric(
|
|
216
|
+
float(metric),
|
|
217
|
+
step=self.step,
|
|
218
|
+
timestamp=datetime.now(timezone.utc),
|
|
219
|
+
attributes=self.attributes,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if self.auto_increment_step:
|
|
223
|
+
self.step += 1
|
|
224
|
+
|
|
225
|
+
return metric
|
dreadnode/object.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class ObjectRef:
|
|
7
|
+
name: str
|
|
8
|
+
label: str
|
|
9
|
+
hash: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ObjectUri:
|
|
14
|
+
hash: str
|
|
15
|
+
schema_hash: str
|
|
16
|
+
uri: str
|
|
17
|
+
size: int
|
|
18
|
+
type: t.Literal["uri"] = "uri"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ObjectVal:
|
|
23
|
+
hash: str
|
|
24
|
+
schema_hash: str
|
|
25
|
+
value: t.Any
|
|
26
|
+
type: t.Literal["val"] = "val"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Object = ObjectUri | ObjectVal
|
dreadnode/py.typed
ADDED
|
File without changes
|