Skip to content

Commit 2b75ff8

Browse files
authored
fix: support finish call multiple times (#1533)
1 parent ad3d25a commit 2b75ff8

File tree

3 files changed

+32
-3
lines changed

3 files changed

+32
-3
lines changed

swanlab/data/run/main.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class SwanLabRun:
3333
There should be only one instance of the SwanLabRun class for each experiment.
3434
"""
3535

36+
_initialized: bool = False
37+
3638
def __init__(
3739
self,
3840
metadata: dict = None,
@@ -54,6 +56,7 @@ def __init__(
5456
if self.is_started():
5557
raise RuntimeError("SwanLabRun has been initialized")
5658
global run, config
59+
SwanLabRun._initialized = True
5760
run_store = get_run_store()
5861
# ---------------------------------- 初始化类内参数 ----------------------------------
5962
operator = operator or SwanLabRunOperator()
@@ -232,7 +235,11 @@ def finish(state: SwanLabRunState = SwanLabRunState.SUCCESS, error=None, interru
232235
# 5. 返回old_run
233236
# 上述步骤中只有客户端对象 client 不清空,其余全局变量全部清理
234237
if run is None:
235-
raise RuntimeError("The run object is None, please call `swanlab.init` first.")
238+
if SwanLabRun._initialized:
239+
# 已经 finish 过,静默返回(幂等)
240+
swanlog.warning("The run object is already finished.")
241+
return
242+
return
236243
if state == SwanLabRunState.CRASHED and error is None:
237244
raise ValueError("When the state is 'CRASHED', the error message cannot be None.")
238245
error = error if state == SwanLabRunState.CRASHED else None

swanlab/data/sdk.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,6 @@ def log(
470470
return ll
471471

472472

473-
@should_call_after_init("You must call swanlab.init() before using finish()")
474473
def finish(state: SwanLabRunState = SwanLabRunState.SUCCESS, error=None):
475474
"""
476475
Finish the current run and close the current experiment
@@ -480,8 +479,12 @@ def finish(state: SwanLabRunState = SwanLabRunState.SUCCESS, error=None):
480479
If you mark the experiment as 'CRASHED' manually, `error` must be provided.
481480
"""
482481
run = get_run()
482+
if run is None:
483+
if not SwanLabRun._initialized:
484+
raise RuntimeError("You must call swanlab.init() before using finish()")
485+
return swanlog.warning("The run object is already finished.")
483486
if not run.running:
484-
return swanlog.error("After experiment is finished, you can't call finish() again.")
487+
return swanlog.warning("The run object is already finished.")
485488
run.finish(state, error)
486489

487490

test/unit/data/test_sdk.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010
import os
1111
import random
12+
from unittest.mock import Mock
1213

1314
import platformdirs
1415
import pytest
@@ -45,6 +46,24 @@ def setup_function():
4546
MODE = SwanLabEnv.MODE.value
4647

4748

49+
def test_finish_before_init_raises_runtime_error(monkeypatch):
50+
monkeypatch.setattr(S.SwanLabRun, "_initialized", False)
51+
52+
with pytest.raises(RuntimeError, match=r"You must call swanlab\.init\(\) before using finish\(\)"):
53+
S.finish()
54+
55+
56+
def test_finish_after_init_warns_when_called_twice(monkeypatch):
57+
warning_mock = Mock()
58+
monkeypatch.setattr(swanlog, "warning", warning_mock)
59+
60+
S.init(mode="local")
61+
S.finish()
62+
S.finish()
63+
64+
warning_mock.assert_any_call("The run object is already finished.")
65+
66+
4867
class TestInitModeFunc:
4968

5069
def test_init_error_mode(self):

0 commit comments

Comments
 (0)