expr-tracker 0.3.0__tar.gz → 0.3.1__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 (113) hide show
  1. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/PKG-INFO +1 -1
  2. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/design.md +5 -0
  3. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/backends.md +6 -4
  4. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/run.py +20 -22
  5. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_artifacts.py +2 -2
  6. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_hot_paths.py +1 -1
  7. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_review_regressions.py +2 -2
  8. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_run_backends.py +24 -4
  9. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_trackio.py +4 -2
  10. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_wandb.py +2 -2
  11. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/.github/workflows/docs.yaml +0 -0
  12. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/.github/workflows/release.yaml +0 -0
  13. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/.gitignore +0 -0
  14. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/LICENSE +0 -0
  15. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/README.md +0 -0
  16. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/architecture.md +0 -0
  17. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/examples.md +0 -0
  18. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/getting-started.md +0 -0
  19. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/alerts.md +0 -0
  20. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/artifacts.md +0 -0
  21. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/cli.md +0 -0
  22. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/distributed.md +0 -0
  23. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/history.md +0 -0
  24. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/logging.md +0 -0
  25. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/spans.md +0 -0
  26. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/guide/streams.md +0 -0
  27. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/index.md +0 -0
  28. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/reference/api.md +0 -0
  29. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/reference/configuration.md +0 -0
  30. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/docs/reference/expressions.md +0 -0
  31. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/README.md +0 -0
  32. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/alert_rules.py +0 -0
  33. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/checkpoints.py +0 -0
  34. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/early_stopping.py +0 -0
  35. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/multiprocess_pipeline.py +0 -0
  36. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/profile_step.py +0 -0
  37. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/examples/quickstart.py +0 -0
  38. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/mkdocs.yml +0 -0
  39. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/pyproject.toml +0 -0
  40. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/__init__.py +0 -0
  41. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/_compat.py +0 -0
  42. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/__init__.py +0 -0
  43. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/backends/__init__.py +0 -0
  44. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/backends/base.py +0 -0
  45. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/backends/cards.py +0 -0
  46. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/dispatch.py +0 -0
  47. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/engine.py +0 -0
  48. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/__init__.py +0 -0
  49. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/eval.py +0 -0
  50. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/functions.py +0 -0
  51. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/lexer.py +0 -0
  52. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/nodes.py +0 -0
  53. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/parser.py +0 -0
  54. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/expr/rule.py +0 -0
  55. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/alerts/models.py +0 -0
  56. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/artifacts.py +0 -0
  57. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/cli.py +0 -0
  58. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/encoders.py +0 -0
  59. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/__init__.py +0 -0
  60. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/codec.py +0 -0
  61. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/frame.py +0 -0
  62. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/naming.py +0 -0
  63. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/reader.py +0 -0
  64. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/series.py +0 -0
  65. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/store.py +0 -0
  66. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/history/writer.py +0 -0
  67. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/plugins.py +0 -0
  68. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/py.typed +0 -0
  69. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/spans.py +0 -0
  70. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/summary.py +0 -0
  71. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/trace.py +0 -0
  72. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/tracker.py +0 -0
  73. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/src/expr_tracker/types.py +0 -0
  74. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/conftest.py +0 -0
  75. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_backends.py +0 -0
  76. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_delivery.py +0 -0
  77. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_dispatch.py +0 -0
  78. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_engine.py +0 -0
  79. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_models.py +0 -0
  80. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_alert_routing.py +0 -0
  81. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_benchmark.py +0 -0
  82. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_cache.py +0 -0
  83. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_cli.py +0 -0
  84. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_correctness.py +0 -0
  85. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_distributed.py +0 -0
  86. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_e2e.py +0 -0
  87. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_examples.py +0 -0
  88. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_expr_builder.py +0 -0
  89. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_expr_eval.py +0 -0
  90. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_expr_functions.py +0 -0
  91. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_expr_parser.py +0 -0
  92. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_expr_properties.py +0 -0
  93. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_failure_modes.py +0 -0
  94. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_features.py +0 -0
  95. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_frame_codec_summary.py +0 -0
  96. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_history.py +0 -0
  97. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_integration.py +0 -0
  98. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_lark.py +0 -0
  99. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_perf.py +0 -0
  100. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_public_surfaces.py +0 -0
  101. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_rule_lifecycle.py +0 -0
  102. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_run_dir.py +0 -0
  103. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_scenarios.py +0 -0
  104. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_span_plugins.py +0 -0
  105. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_spans.py +0 -0
  106. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_step_commit.py +0 -0
  107. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_streams.py +0 -0
  108. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_stress.py +0 -0
  109. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_trace.py +0 -0
  110. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_value_encoding.py +0 -0
  111. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_writer_buffer.py +0 -0
  112. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/tests/test_writer_durability.py +0 -0
  113. {expr_tracker-0.3.0 → expr_tracker-0.3.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: expr_tracker
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Local-first experiment tracking with queryable history and expression-based alerts on your training metrics
5
5
  Project-URL: Homepage, https://hspk.github.io/expr_tracker/
6
6
  Project-URL: Documentation, https://hspk.github.io/expr_tracker/
@@ -118,6 +118,11 @@ so timing a region adds no row. The tree, with timestamps and attributes, goes t
118
118
  `spans[.stream][.rankN].jsonl` through a second `JsonlWriter`, enqueued rather
119
119
  than appended so a span does not pay for a flush decision of its own.
120
120
 
121
+ The committed history row is also the fan-out boundary. Summary, alerts and
122
+ remote backends consume the exact row emitted by `HistoryStore`, rather than the
123
+ individual `log()` calls that assembled it. Span and plugin metrics therefore
124
+ reach every sink without a second metric path.
125
+
121
126
  Plugins hang off the same two hooks. `start` runs before the clock starts and
122
127
  `end` after it stops, so a plugin never inflates the duration it reports on; what
123
128
  `end` returns is merged into the span's metrics under the span's own path, which
@@ -16,15 +16,17 @@ optional.
16
16
 
17
17
  | Call | Forwarded as |
18
18
  | --- | --- |
19
- | `et.log(data, step, commit)` | `backend.log(data, step=<resolved>, commit=<resolved>)` |
19
+ | committed history row | `backend.log(metrics, step=<resolved>, commit=True)` |
20
20
  | `et.init(...)` | `backend.init(project, name, config, entity, tags, notes, resume, ...)` |
21
21
  | `et.define_metric(name, **kw)` | `backend.define_metric` if it has one |
22
22
  | `et.log_artifact(...)` | `backend.log_artifact` if it has one |
23
23
  | `et.finish(exit_code)` | `backend.finish()` |
24
24
 
25
- The **resolved** step matters: two `log()` calls for one step stay one step on the
26
- backend too, and a step dropped by the local step policy is never forwarded. A
27
- backend that does not accept `commit` (trackio) simply does not receive it.
25
+ Local history assembles the row first. Summary, alerts and remote backends then
26
+ receive that same committed row, including span and plugin metrics. Two `log()`
27
+ calls for one step therefore produce one backend call, and a step dropped by the
28
+ local step policy is never forwarded. A backend that does not accept `commit`
29
+ (trackio) simply does not receive it.
28
30
 
29
31
  ## Failure handling
30
32
 
@@ -15,7 +15,7 @@ from typing import Any, Literal
15
15
  from loguru import logger
16
16
 
17
17
  from .artifacts import Artifact, ArtifactStore, coerce_artifact
18
- from .history import HistoryStore, current_rank, resolve_commit
18
+ from .history import HistoryStore, current_rank
19
19
  from .history.naming import resolve_artifact_root, sidecar_filename
20
20
  from .summary import Summary
21
21
 
@@ -245,34 +245,32 @@ class Run:
245
245
  # ------------------------------------------------------------------ logging
246
246
 
247
247
  def _on_commit(self, record: dict):
248
- if self.alerts is not None:
249
- self.alerts.on_step(record)
250
-
251
- def log(self, data: dict, step: int | None = None, commit: bool | None = None):
252
- """Log metrics, mirroring ``wandb.log(data, step=..., commit=...)``.
253
-
254
- All sinks share one timeline: if local history rejects the call (closed run,
255
- or a backward step), the summary and remote backends are skipped too.
256
- """
257
- resolved_step = self.history.log(data, step=step, commit=commit)
258
- if resolved_step is None:
259
- return
248
+ metrics = {
249
+ key: value
250
+ for key, value in record.items()
251
+ if isinstance(key, str) and not key.startswith("_")
252
+ }
253
+ step = record["_step"]
260
254
  try:
261
- self.summary.observe(data)
255
+ self.summary.observe(metrics)
262
256
  except Exception as e: # a sink must never break the training loop
263
257
  logger.warning(f"Failed to update summary: {e}")
264
258
  for name, backend in self.backends.items():
265
- # Forward the resolved step and commit so a backend's row layout
266
- # matches the local history instead of drifting on its own counter.
267
- extra = (
268
- {"commit": resolve_commit(step, commit)}
269
- if self._takes_commit.get(name)
270
- else {}
271
- )
259
+ extra = {"commit": True} if self._takes_commit.get(name) else {}
272
260
  try:
273
- backend.log(data, step=resolved_step, **extra)
261
+ backend.log(metrics, step=step, **extra)
274
262
  except Exception as e:
275
263
  logger.warning(f"Failed to log metrics to {name}: {e}")
264
+ if self.alerts is not None:
265
+ self.alerts.on_step(record)
266
+
267
+ def log(self, data: dict, step: int | None = None, commit: bool | None = None):
268
+ """Log metrics, mirroring ``wandb.log(data, step=..., commit=...)``.
269
+
270
+ Local history owns row assembly. Summary, alerts and remote backends receive
271
+ the same complete row only after it commits, including span/plugin metrics.
272
+ """
273
+ self.history.log(data, step=step, commit=commit)
276
274
 
277
275
  def history_query(self, *args, **kwargs):
278
276
  return self.history.get(*args, **kwargs)
@@ -252,7 +252,7 @@ def test_backend_receives_commit_argument(tmp_path):
252
252
  et.log({"v": 1}, step=3, commit=False)
253
253
  finally:
254
254
  et.finish()
255
- assert calls == [({"v": 1}, 3, False)]
255
+ assert calls == [({"v": 1}, 3, True)]
256
256
 
257
257
 
258
258
  def test_legacy_backend_without_commit_still_works(tmp_path):
@@ -313,7 +313,7 @@ def test_rejected_step_reaches_no_sink(tmp_path):
313
313
 
314
314
  et.init(project="p", name="mono", dir=str(tmp_path), backends=[Backend()])
315
315
  try:
316
- et.log({"loss": 1.0}, step=10)
316
+ et.log({"loss": 1.0}, step=10, commit=True)
317
317
  et.log({"loss": 99.0}, step=5) # rejected under the default step policy
318
318
  assert forwarded == [{"loss": 1.0}]
319
319
  assert dict(et.summary()) == {"loss": 1.0}
@@ -236,7 +236,7 @@ def test_the_summary_survives_finish(run, tmp_path):
236
236
 
237
237
  def test_a_dropped_step_does_not_touch_the_summary(run):
238
238
  run()
239
- et.log({"loss": 1.0}, step=5)
239
+ et.log({"loss": 1.0}, step=5, commit=True)
240
240
  et.log({"loss": 99.0}, step=1)
241
241
  assert dict(et.summary())["loss"] == 1.0
242
242
 
@@ -473,11 +473,11 @@ def test_alias_added_on_a_dedup_reuse_is_persisted(tmp_path):
473
473
  et.finish()
474
474
 
475
475
 
476
- def test_summary_ignores_non_string_keys(tmp_path):
476
+ def test_summary_matches_history_key_encoding(tmp_path):
477
477
  et.init(project="p", name="s", dir=str(tmp_path), backends=[])
478
478
  try:
479
479
  et.log({(1, 2): 3, b"k": 4, "loss": 5})
480
- assert dict(et.summary()) == {"loss": 5}
480
+ assert dict(et.summary()) == {"(1, 2)": 3, "k": 4, "loss": 5}
481
481
  finally:
482
482
  et.finish()
483
483
 
@@ -137,7 +137,7 @@ def test_backend_receives_init_log_and_finish(tmp_path):
137
137
  tags=["a"],
138
138
  notes="hello",
139
139
  )
140
- run.log({"loss": 1.0}, step=0)
140
+ run.log({"loss": 1.0}, step=0, commit=True)
141
141
  run.define_metric("loss", summary="min")
142
142
  run.finish()
143
143
 
@@ -146,8 +146,7 @@ def test_backend_receives_init_log_and_finish(tmp_path):
146
146
  assert init_kwargs["project"] == "p" and init_kwargs["config"] == {"lr": 0.1}
147
147
  assert init_kwargs["tags"] == ["a"] and init_kwargs["notes"] == "hello"
148
148
  assert backend.calls[1][1] == ({"loss": 1.0},)
149
- # an explicit step defers the commit locally, so the backend defers it too
150
- assert backend.calls[1][2] == {"step": 0, "commit": False}
149
+ assert backend.calls[1][2] == {"step": 0, "commit": True}
151
150
 
152
151
 
153
152
  def test_backend_is_named_after_its_class(tmp_path):
@@ -286,7 +285,7 @@ def test_rejected_step_reaches_no_sink(tmp_path):
286
285
  backend = FakeBackend()
287
286
  run = Run(project="p", name="r", dir=str(tmp_path), backends=[backend])
288
287
  try:
289
- run.log({"loss": 1.0}, step=5)
288
+ run.log({"loss": 1.0}, step=5, commit=True)
290
289
  run.log({"loss": 2.0}, step=1) # backwards: dropped
291
290
  assert [c[1][0] for c in run.backends["fakebackend"].calls[1:]] == [
292
291
  {"loss": 1.0}
@@ -296,6 +295,27 @@ def test_rejected_step_reaches_no_sink(tmp_path):
296
295
  run.finish()
297
296
 
298
297
 
298
+ def test_committed_row_fans_out_span_metrics(tmp_path):
299
+ backend = FakeBackend()
300
+ et.init(project="p", name="r", dir=str(tmp_path), backends=[backend])
301
+ try:
302
+ et.log({"loss": 1.0}, commit=False)
303
+ with et.span("forward"):
304
+ pass
305
+ et.log({"accuracy": 0.5})
306
+
307
+ log_call = backend.calls[1]
308
+ metrics = log_call[1][0]
309
+ assert metrics["loss"] == 1.0
310
+ assert metrics["accuracy"] == 0.5
311
+ assert metrics["count/forward"] == 1
312
+ assert "time_ms/forward" in metrics
313
+ assert et.summary()["count/forward"] == 1
314
+ assert "time_ms/forward" in et.summary()
315
+ finally:
316
+ et.finish()
317
+
318
+
299
319
  def test_finish_is_idempotent(tmp_path):
300
320
  backend = FakeBackend()
301
321
  run = Run(project="p", name="r", dir=str(tmp_path), backends=[backend])
@@ -140,9 +140,11 @@ def test_trackio_receives_the_resolved_step(tmp_path, fake_trackio):
140
140
  run.log({"loss": 1.0})
141
141
  run.log({"acc": 0.5}, step=0, commit=False)
142
142
  run.log({"acc2": 0.6}, step=0)
143
+ run.history.flush(commit_open=True)
143
144
  logs = [call for name, call in fake_trackio.calls if name == "log"]
144
- assert [entry["step"] for entry in logs] == [0, 0, 0]
145
+ assert [entry["step"] for entry in logs] == [0, 0]
145
146
  assert logs[0]["metrics"] == {"loss": 1.0}
147
+ assert logs[1]["metrics"] == {"acc": 0.5, "acc2": 0.6}
146
148
  finally:
147
149
  run.finish()
148
150
 
@@ -150,7 +152,7 @@ def test_trackio_receives_the_resolved_step(tmp_path, fake_trackio):
150
152
  def test_a_dropped_step_never_reaches_trackio(tmp_path, fake_trackio):
151
153
  run = Run(project="p", name="r", dir=str(tmp_path), backends=["trackio"])
152
154
  try:
153
- run.log({"loss": 1.0}, step=5)
155
+ run.log({"loss": 1.0}, step=5, commit=True)
154
156
  run.log({"loss": 2.0}, step=1) # backwards: dropped locally
155
157
  logs = [call for name, call in fake_trackio.calls if name == "log"]
156
158
  assert [entry["step"] for entry in logs] == [5]
@@ -142,9 +142,9 @@ def test_several_logs_for_one_step_stay_one_wandb_step(run):
142
142
  def test_an_explicit_step_defers_the_commit_on_wandb_too(run):
143
143
  instance = run(name="explicit")
144
144
  instance.log({"a": 1}, step=7)
145
- assert wandb.run.step == 7 # positioned, not yet advanced
145
+ assert wandb.run.step == 0
146
146
  instance.log({"b": 2}, step=7)
147
- assert wandb.run.step == 7
147
+ assert wandb.run.step == 0
148
148
  instance.log({"c": 3}, step=8, commit=True)
149
149
  assert wandb.run.step == 9
150
150
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes