dreadnode 1.0.3__tar.gz → 1.0.4__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 (28) hide show
  1. {dreadnode-1.0.3 → dreadnode-1.0.4}/PKG-INFO +1 -1
  2. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/__init__.py +13 -3
  3. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/main.py +39 -9
  4. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/metric.py +3 -2
  5. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/tracing/span.py +39 -35
  6. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/util.py +8 -0
  7. {dreadnode-1.0.3 → dreadnode-1.0.4}/pyproject.toml +2 -2
  8. {dreadnode-1.0.3 → dreadnode-1.0.4}/README.md +0 -0
  9. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/api/__init__.py +0 -0
  10. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/api/client.py +0 -0
  11. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/api/models.py +0 -0
  12. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/api/util.py +0 -0
  13. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/artifact/__init__.py +0 -0
  14. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/artifact/merger.py +0 -0
  15. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/artifact/storage.py +0 -0
  16. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/artifact/tree_builder.py +0 -0
  17. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/constants.py +0 -0
  18. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/integrations/__init__.py +0 -0
  19. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/integrations/transformers.py +0 -0
  20. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/object.py +0 -0
  21. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/py.typed +0 -0
  22. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/serialization.py +0 -0
  23. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/task.py +0 -0
  24. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/tracing/__init__.py +0 -0
  25. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/tracing/constants.py +0 -0
  26. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/tracing/exporters.py +0 -0
  27. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/types.py +0 -0
  28. {dreadnode-1.0.3 → dreadnode-1.0.4}/dreadnode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dreadnode
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Dreadnode SDK
5
5
  Author: Nick Landers
6
6
  Author-email: monoxgas@gmail.com
@@ -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
- "__version__",
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
  ]
@@ -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:
@@ -503,7 +502,7 @@ class Dreadnode:
503
502
  _label = label or func_name
504
503
 
505
504
  # conform our label for sanity
506
- _label = re.sub(r"[\W_]+", "_", _label.lower())
505
+ _label = clean_str(_label)
507
506
 
508
507
  _attributes = attributes or {}
509
508
  _attributes["code.function"] = func_name
@@ -570,7 +569,7 @@ class Dreadnode:
570
569
  if (run := current_run_span.get()) is None:
571
570
  raise RuntimeError("Task spans must be created within a run")
572
571
 
573
- label = label or re.sub(r"[\W_]+", "_", name.lower())
572
+ label = label or clean_str(name)
574
573
  return TaskSpan(
575
574
  name=name,
576
575
  label=label,
@@ -681,6 +680,31 @@ class Dreadnode:
681
680
  autolog=autolog,
682
681
  )
683
682
 
683
+ def tag(self, *tag: str, to: ToObject = "task-or-run") -> None:
684
+ """
685
+ Add one or many tags to the current task or run.
686
+
687
+ Example:
688
+ ```
689
+ with dreadnode.run("my_run") as run:
690
+ run.tag("my_tag")
691
+ ```
692
+
693
+ Args:
694
+ tag: The tag to attach to the task or run.
695
+ to: The target object to log the tag to. Can be "task-or-run" or "run".
696
+ Defaults to "task-or-run". If "task-or-run", the tag will be logged
697
+ to the current task or run, whichever is the nearest ancestor.
698
+ """
699
+ task = current_task_span.get()
700
+ run = current_run_span.get()
701
+
702
+ target = (task or run) if to == "task-or-run" else run
703
+ if target is None:
704
+ raise RuntimeError("Tagging must be done within a run")
705
+
706
+ target.add_tags(tag)
707
+
684
708
  @handle_internal_errors()
685
709
  def push_update(self) -> None:
686
710
  """
@@ -777,7 +801,7 @@ class Dreadnode:
777
801
  mode: MetricAggMode | None = None,
778
802
  attributes: JsonDict | None = None,
779
803
  to: ToObject = "task-or-run",
780
- ) -> None:
804
+ ) -> Metric:
781
805
  """
782
806
  Log a single metric to the current task or run.
783
807
 
@@ -809,6 +833,9 @@ class Dreadnode:
809
833
  to: The target object to log the metric to. Can be "task-or-run" or "run".
810
834
  Defaults to "task-or-run". If "task-or-run", the metric will be logged
811
835
  to the current task or run, whichever is the nearest ancestor.
836
+
837
+ Returns:
838
+ The logged metric object.
812
839
  """
813
840
 
814
841
  @t.overload
@@ -820,7 +847,7 @@ class Dreadnode:
820
847
  origin: t.Any | None = None,
821
848
  mode: MetricAggMode | None = None,
822
849
  to: ToObject = "task-or-run",
823
- ) -> None:
850
+ ) -> Metric:
824
851
  """
825
852
  Log a single metric to the current task or run.
826
853
 
@@ -848,6 +875,9 @@ class Dreadnode:
848
875
  to: The target object to log the metric to. Can be "task-or-run" or "run".
849
876
  Defaults to "task-or-run". If "task-or-run", the metric will be logged
850
877
  to the current task or run, whichever is the nearest ancestor.
878
+
879
+ Returns:
880
+ The logged metric object.
851
881
  """
852
882
 
853
883
  @handle_internal_errors()
@@ -862,7 +892,7 @@ class Dreadnode:
862
892
  mode: MetricAggMode | None = None,
863
893
  attributes: JsonDict | None = None,
864
894
  to: ToObject = "task-or-run",
865
- ) -> None:
895
+ ) -> Metric:
866
896
  task = current_task_span.get()
867
897
  run = current_run_span.get()
868
898
 
@@ -877,7 +907,7 @@ class Dreadnode:
877
907
  float(value), step, timestamp or datetime.now(timezone.utc), attributes or {}
878
908
  )
879
909
  )
880
- target.log_metric(key, metric, origin=origin, mode=mode)
910
+ return target.log_metric(key, metric, origin=origin, mode=mode)
881
911
 
882
912
  @handle_internal_errors()
883
913
  def log_artifact(
@@ -926,7 +956,7 @@ class Dreadnode:
926
956
  def log_input(
927
957
  self,
928
958
  name: str,
929
- value: JsonValue,
959
+ value: t.Any,
930
960
  *,
931
961
  label: str | None = None,
932
962
  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 "inc".
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
- self.value += max(prior_values)
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_: uniquify_sequence(tags or ()),
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
- @property
171
- def tags(self) -> tuple[str, ...]:
172
- return tuple(self.get_attribute(SPAN_ATTRIBUTE_TAGS_, ()))
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
- @tags.setter
175
- def tags(self, new_tags: t.Sequence[str]) -> None:
176
- self.set_attribute(SPAN_ATTRIBUTE_TAGS_, uniquify_sequence(new_tags))
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 re.sub(r"\W+", "_", name.lower())
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
- ) -> None: ...
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
- ) -> None: ...
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
- ) -> None:
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 = re.sub(r"[^\w/]+", "_", key.lower())
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 re.sub(r"\W+", "_", name.lower())
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 re.sub(r"\W+", "_", name.lower())
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 re.sub(r"\W+", "_", name.lower())
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
- ) -> None: ...
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
- ) -> None: ...
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
- ) -> None:
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 = re.sub(r"[^\w/]+", "_", key.lower())
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
- # Don't include `source` and `mode` as we handled it here.
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"
3
+ version = "1.0.4"
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.3"
9
+ version = "1.0.4"
10
10
  description = "Dreadnode SDK"
11
11
  authors = ["Nick Landers <monoxgas@gmail.com>"]
12
12
  repository = "https://github.com/dreadnode/sdk"
File without changes
File without changes
File without changes
File without changes
File without changes