Repository: IDSIA/sacred (python)

Issue:
save_config with config_filename=mine.yaml fails
following similar things like the command line doc: https://sacred.readthedocs.io/en/stable/command_line.html

`python 2_hello_named_updates.py save_config with config_filename=mine.yaml` gives below error:

>WARNING - root - Added new config entry: "config_filename"
INFO - 2_hello_named_updates - Running command 'save_config'
INFO - 2_hello_named_updates - Started
ERROR - 2_hello_named_updates - Failed after 0:00:00!
Traceback (most recent calls WITHOUT Sacred internals):
sacred.utils.SacredError: The configuration is read-only in a captured function!



Pull request description:
Fix save_config command (Fixes #747)
Fixes #747 by copying config before deleting reserved key save_config

Patch:
diff --git a/sacred/commands.py b/sacred/commands.py
index 71cb7b8..e1becc5 100644
--- a/sacred/commands.py
+++ b/sacred/commands.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 # coding=utf-8
 """Defines the stock-commands that every sacred experiment ships with."""
+import copy
 
 import pprint
 import pydoc
@@ -169,6 +170,8 @@ def save_config(_config, _log, config_filename="config.json"):
     By default uses the filename "config.json", but that can be changed by
     setting the config_filename config entry.
     """
+    # Copy the config to make it mutable
+    _config = copy.deepcopy(_config)
     if "config_filename" in _config:
         del _config["config_filename"]
     _log.info('Saving config to "{}"'.format(config_filename))


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index c7e5095..6316180 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -1,7 +1,10 @@
 #!/usr/bin/env python
 # coding=utf-8
-
+import os
+import sys
+import tempfile
 import pprint
+import json
 from collections import OrderedDict
 
 import pytest
@@ -24,6 +27,7 @@ from sacred.commands import (
 )
 from sacred.config import ConfigScope
 from sacred.config.config_summary import ConfigSummary
+import sacred.optional as opt
 
 
 def test_non_unicode_repr():
@@ -236,3 +240,38 @@ def test_format_named_configs():
     assert "# named config with doc" in named_configs_text
     assert "ingred.named_config1" in named_configs_text
     assert "ingred.dict_config" in named_configs_text
+
+
+@pytest.mark.skipif(
+    sys.platform == "win32",
+    reason="Windows machines on azure don't support temporary directories",
+)
+def test_save_config():
+    ex = Experiment(name="experiment")
+
+    config = {"a": 1, "b": 2, "seed": 42}
+    ex.add_config(config)
+
+    @ex.main
+    def main():
+        pass
+
+    cwd = os.getcwd()
+    try:
+        with tempfile.TemporaryDirectory() as dirname:
+            os.chdir(dirname)
+
+            ex.run("save_config")
+
+            with open(os.path.join(dirname, "config.json")) as f:
+                saved_config = json.load(f)
+                assert config == saved_config
+
+            if opt.has_yaml:
+                ex.run("save_config", {"config_filename": "config.yaml"})
+
+                with open(os.path.join(dirname, "config.yaml")) as f:
+                    saved_config = opt.yaml.load(f)
+                    assert config == saved_config
+    finally:
+        os.chdir(cwd)