Skip to content

Commit 7e4e8c2

Browse files
authored
retrieve previously logged experiment metadata (#11)
1 parent 1ddd800 commit 7e4e8c2

File tree

4 files changed

+158
-3
lines changed

4 files changed

+158
-3
lines changed

src/litlogger/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from litlogger.experiment import Experiment
2727

2828
# Import SDK functions
29-
from litlogger.init import finish, init
29+
from litlogger.init import finish, get_metadata, init
3030

3131
# Global variables
3232
experiment: Experiment | None = None
@@ -54,6 +54,7 @@
5454
"log_model_artifact",
5555
"get_model_artifact",
5656
"finalize",
57+
"get_metadata",
5758
]
5859

5960
try:

src/litlogger/experiment.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ def __init__(
9292
self._finalized = False
9393
self.store_step = store_step
9494
self.store_created_at = store_created_at
95-
self._metadata = metadata
9695

9796
# Initialize printer and stats tracking
9897
self._printer = Printer(verbose=verbose)
@@ -201,6 +200,16 @@ def teamspace(self) -> "Teamspace":
201200
"""
202201
return self._teamspace
203202

203+
@property
204+
def metadata(self) -> Dict[str, str]:
205+
"""Get the metadata associated with this experiment from the metrics stream.
206+
207+
Returns:
208+
Dict[str, str]: The metadata dictionary with key-value pairs from code-defined tags.
209+
"""
210+
tags = getattr(self._metrics_store, "tags", None) or []
211+
return {tag.name: tag.value for tag in tags if tag.from_code}
212+
204213
def log_metrics(self, metrics: Dict[str, float], step: int | None = None, **kwargs: float) -> None:
205214
"""Log metrics to the experiment with background uploading.
206215
@@ -559,5 +568,5 @@ def print_url(self) -> None:
559568
teamspace=self._teamspace.name,
560569
url=self._url,
561570
version=self.version,
562-
metadata=self._metadata,
571+
metadata=self.metadata,
563572
)

src/litlogger/init.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,20 @@ def finish(status: str | None = None) -> None:
141141
raise RuntimeError("You must call litlogger.init() before litlogger.finish()")
142142

143143
litlogger.experiment.finalize(status)
144+
145+
146+
def get_metadata() -> Dict[str, str]:
147+
"""Get the metadata associated with the current experiment.
148+
149+
Returns:
150+
Dict[str, str]: The metadata dictionary with key-value pairs from the metrics stream.
151+
152+
Raises:
153+
RuntimeError: If litlogger.init() has not been called.
154+
"""
155+
import litlogger
156+
157+
if litlogger.experiment is None:
158+
raise RuntimeError("You must call litlogger.init() before litlogger.get_metadata()")
159+
160+
return litlogger.experiment.metadata

tests/integrations/test_standalone.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ def test_error_handling_before_init():
358358
with pytest.raises(RuntimeError, match="You must call litlogger.init\\(\\)"):
359359
litlogger.finalize()
360360

361+
with pytest.raises(RuntimeError, match="You must call litlogger.init\\(\\)"):
362+
litlogger.get_metadata()
363+
361364

362365
@pytest.mark.cloud()
363366
def test_multiple_experiments_in_sequence():
@@ -498,6 +501,131 @@ def test_metadata_and_tags():
498501
)
499502

500503

504+
@pytest.mark.cloud()
505+
def test_get_metadata():
506+
"""Test get_metadata() function and experiment.metadata property."""
507+
experiment_name = f"meta_test-{uuid.uuid4().hex}"
508+
metadata = {
509+
"model": "GPT-2",
510+
"dataset": "WikiText",
511+
"learning_rate": "0.0001",
512+
"batch_size": "16",
513+
}
514+
515+
exp = litlogger.init(
516+
name=experiment_name,
517+
teamspace="oss-litlogger",
518+
metadata=metadata,
519+
)
520+
521+
# Test experiment.metadata property returns the metadata from the metrics stream
522+
retrieved_metadata = exp.metadata
523+
assert isinstance(retrieved_metadata, dict)
524+
for key, value in metadata.items():
525+
assert key in retrieved_metadata, f"Expected key '{key}' in metadata"
526+
assert (
527+
retrieved_metadata[key] == value
528+
), f"Expected metadata['{key}'] == '{value}', got '{retrieved_metadata[key]}'"
529+
530+
# Test litlogger.get_metadata() returns the same metadata
531+
global_metadata = litlogger.get_metadata()
532+
assert isinstance(global_metadata, dict)
533+
for key, value in metadata.items():
534+
assert key in global_metadata, f"Expected key '{key}' in global metadata"
535+
assert global_metadata[key] == value
536+
537+
# Verify both return the same data
538+
assert retrieved_metadata == global_metadata
539+
540+
litlogger.finalize()
541+
542+
# Cleanup
543+
project_id = exp._teamspace.id
544+
stream_id = exp._metrics_store.id
545+
546+
client = LitRestClient()
547+
client.lit_logger_service_delete_metrics_stream(
548+
project_id=project_id,
549+
body=LitLoggerServiceDeleteMetricsStreamBody(ids=[stream_id]),
550+
)
551+
552+
553+
@pytest.mark.cloud()
554+
def test_get_metadata_empty():
555+
"""Test get_metadata() when no metadata is provided."""
556+
experiment_name = f"meta_empty_test-{uuid.uuid4().hex}"
557+
558+
exp = litlogger.init(
559+
name=experiment_name,
560+
teamspace="oss-litlogger",
561+
)
562+
563+
# Test that metadata is empty dict when none provided
564+
retrieved_metadata = exp.metadata
565+
assert isinstance(retrieved_metadata, dict)
566+
assert len(retrieved_metadata) == 0
567+
568+
# Test litlogger.get_metadata() also returns empty dict
569+
global_metadata = litlogger.get_metadata()
570+
assert isinstance(global_metadata, dict)
571+
assert len(global_metadata) == 0
572+
573+
litlogger.finalize()
574+
575+
# Cleanup
576+
project_id = exp._teamspace.id
577+
stream_id = exp._metrics_store.id
578+
579+
client = LitRestClient()
580+
client.lit_logger_service_delete_metrics_stream(
581+
project_id=project_id,
582+
body=LitLoggerServiceDeleteMetricsStreamBody(ids=[stream_id]),
583+
)
584+
585+
586+
@pytest.mark.cloud()
587+
def test_get_metadata_direct_experiment():
588+
"""Test experiment.metadata property when using Experiment class directly."""
589+
from datetime import datetime, timezone
590+
591+
experiment_name = f"meta_direct-{uuid.uuid4().hex}"
592+
timestamp = datetime.now(timezone.utc).isoformat(timespec="milliseconds")
593+
version_str = timestamp.replace("+00:00", "Z")
594+
595+
metadata = {
596+
"framework": "PyTorch",
597+
"version": "2.0",
598+
}
599+
600+
with tempfile.TemporaryDirectory() as tmpdir:
601+
exp = Experiment(
602+
name=experiment_name,
603+
version=version_str,
604+
teamspace="oss-litlogger",
605+
log_dir=tmpdir,
606+
metadata=metadata,
607+
)
608+
609+
# Test experiment.metadata property
610+
retrieved_metadata = exp.metadata
611+
assert isinstance(retrieved_metadata, dict)
612+
for key, value in metadata.items():
613+
assert key in retrieved_metadata
614+
assert retrieved_metadata[key] == value
615+
616+
exp.finalize()
617+
618+
# Cleanup
619+
project_id = exp._teamspace.id
620+
stream_id = exp._metrics_store.id
621+
622+
client = LitRestClient()
623+
client.lit_logger_service_delete_metrics_stream(
624+
project_id=project_id,
625+
body=LitLoggerServiceDeleteMetricsStreamBody(ids=[stream_id]),
626+
)
627+
628+
501629
@pytest.mark.cloud()
502630
def test_custom_colors():
503631
"""Test experiments with custom colors."""

0 commit comments

Comments
 (0)