Repository: omry/omegaconf

Issue:
Deprecatd config.save() in favor of OmegaConf.save()
Since Config is a dynamic data type, I want to minimize the functions that exists on it to the bare minimum.
A user can easily create a save field that would potentially conflict with the save() function.
A future version would remove config.save() completely.


Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 107](https://coveralls.io/builds/26851120)

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at ?**%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/26851120/badge)](https://coveralls.io/builds/26851120) |
| :-- | --: |
| Change from base [Build 99](https://coveralls.io/builds/26825314): |  0.0% |
| Covered Lines: |  |
| Relevant Lines: | 0 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index acd7272..dcd4af0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,7 @@ jobs:
   py27_linux:
     docker:
       # nox got requires python 3 but it can test 2.7
-      - image: circleci/python:3.6
+      - image: circleci/python:3.7
     steps:
       - checkout
       - run: echo 'export NOX_PYTHON_VERSIONS=2.7' >> $BASH_ENV
@@ -14,7 +14,7 @@ jobs:
 
   py35_linux:
     docker:
-      - image: circleci/python:3.5
+      - image: circleci/python:3.7
     steps:
       - checkout
       - run: echo 'export NOX_PYTHON_VERSIONS=3.5' >> $BASH_ENV
@@ -23,7 +23,7 @@ jobs:
 
   py36_linux:
     docker:
-      - image: circleci/python:3.6
+      - image: circleci/python:3.7
     steps:
       - checkout
       - run: echo 'export NOX_PYTHON_VERSIONS=3.6' >> $BASH_ENV
@@ -42,7 +42,7 @@ jobs:
   # Misc
   coverage:
     docker:
-      - image: circleci/python:3.6
+      - image: circleci/python:3.7
     steps:
       - checkout
       - run: sudo pip install nox
diff --git a/news/66.removal b/news/66.removal
new file mode 100644
index 0000000..039ff6d
--- /dev/null
+++ b/news/66.removal
@@ -0,0 +1,1 @@
+Deprecated config.save(file) in favor of OmegaConf.save(config, file)
diff --git a/noxfile.py b/noxfile.py
index 6adfd9a..cd8a450 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -26,7 +26,7 @@ def omegaconf(session):
 
 
 # code coverage runs with python 3.6
-@nox.session(python="3.6")
+@nox.session(python="3.7")
 def coverage(session):
     session.install("--upgrade", "setuptools", "pip")
     session.install("coverage", "pytest")
@@ -41,7 +41,7 @@ def coverage(session):
     session.run("coveralls", success_codes=[0, 1])
 
 
-@nox.session(python="3.6")
+@nox.session(python="3.7")
 def lint(session):
     session.install("--upgrade", "setuptools", "pip")
     session.run("pip", "install", ".[lint]", silent=True)
diff --git a/omegaconf/config.py b/omegaconf/config.py
index 2fee5ee..61de58c 100644
--- a/omegaconf/config.py
+++ b/omegaconf/config.py
@@ -1,12 +1,10 @@
 import copy
-import io
-import os
-import re
 import sys
 import warnings
 from abc import abstractmethod
 from collections import defaultdict
 
+import re
 import six
 import yaml
 
@@ -65,15 +63,15 @@ class Config(object):
         self.__dict__["_resolver_cache"] = defaultdict(dict)
 
     def save(self, f):
-        data = self.pretty()
-        if isinstance(f, str):
-            with io.open(os.path.abspath(f), "w", encoding="utf-8") as f:
-                f.write(six.u(data))
-        elif hasattr(f, "write"):
-            f.write(data)
-            f.flush()
-        else:
-            raise TypeError("Unexpected file type")
+        warnings.warn(
+            "Use OmegaConf.save(config, filename) (since 1.4.0)",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        from omegaconf import OmegaConf
+
+        OmegaConf.save(self, f)
 
     def _set_parent(self, parent):
         assert parent is None or isinstance(parent, Config)
diff --git a/omegaconf/omegaconf.py b/omegaconf/omegaconf.py
index a8e75d5..e43f3be 100644
--- a/omegaconf/omegaconf.py
+++ b/omegaconf/omegaconf.py
@@ -1,12 +1,13 @@
 """OmegaConf module"""
 import copy
-import io
 import os
-import re
 import sys
 import warnings
 from contextlib import contextmanager
 
+import io
+import re
+import six
 import yaml
 
 from .config import Config
@@ -72,6 +73,23 @@ class OmegaConf:
         else:
             raise TypeError("Unexpected file type")
 
+    @staticmethod
+    def save(config, f):
+        """
+        Save as configuration object to a file
+        :param config: omegaconf.Config object (DictConfig or ListConfig).
+        :param f: filename or file object
+        """
+        data = config.pretty()
+        if isinstance(f, str):
+            with io.open(os.path.abspath(f), "w", encoding="utf-8") as f:
+                f.write(six.u(data))
+        elif hasattr(f, "write"):
+            f.write(data)
+            f.flush()
+        else:
+            raise TypeError("Unexpected file type")
+
     @staticmethod
     def from_filename(filename):
         warnings.warn(


Test patch:
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 8fc8952..4e73eb0 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -6,7 +6,7 @@ from pytest import raises
 from omegaconf import OmegaConf
 
 
-def save_load_file(conf):
+def save_load_file_deprecated(conf):
     try:
         with tempfile.NamedTemporaryFile(mode="wt", delete=False) as fp:
             conf.save(fp.file)
@@ -28,7 +28,10 @@ def save_load_filename(conf):
         os.unlink(fp.name)
 
 
-def save_load__from_file(conf):
+# Test deprecated config.save()
+
+
+def save_load__from_file_deprecated(conf):
     try:
         with tempfile.NamedTemporaryFile(mode="wt", delete=False) as fp:
             conf.save(fp.file)
@@ -39,7 +42,7 @@ def save_load__from_file(conf):
         os.unlink(fp.name)
 
 
-def save_load__from_filename(conf):
+def save_load__from_filename_deprecated(conf):
     # note that delete=False here is a work around windows incompetence.
     try:
         with tempfile.NamedTemporaryFile(delete=False) as fp:
@@ -50,8 +53,55 @@ def save_load__from_filename(conf):
         os.unlink(fp.name)
 
 
+def test_save_load_file_deprecated():
+    save_load_file_deprecated(OmegaConf.create(dict(a=10)))
+
+
+def test_save_load_filename_deprecated():
+    save_load_filename(OmegaConf.create(dict(a=10)))
+
+
+def test_save_load__from_file_deprecated():
+    save_load__from_file_deprecated(OmegaConf.create(dict(a=10)))
+
+
+def test_save_load__from_filename_deprecated():
+    save_load__from_filename_deprecated(OmegaConf.create(dict(a=10)))
+
+
+def test_save_illegal_type_deprecated():
+    with raises(TypeError):
+        OmegaConf.create().save(1000)
+
+
+##############################################
+# Test OmegaConf.save()
+
+
+def save_load__from_file(conf):
+    try:
+        with tempfile.NamedTemporaryFile(mode="wt", delete=False) as fp:
+            OmegaConf.save(conf, fp.file)
+        with io.open(os.path.abspath(fp.name), "rt") as handle:
+            c2 = OmegaConf.from_file(handle)
+        assert conf == c2
+    finally:
+        os.unlink(fp.name)
+
+
+def save_load__from_filename(conf):
+    # note that delete=False here is a work around windows incompetence.
+    try:
+        with tempfile.NamedTemporaryFile(delete=False) as fp:
+            OmegaConf.save(conf, fp.name)
+            c2 = OmegaConf.from_filename(fp.name)
+            assert conf == c2
+    finally:
+        os.unlink(fp.name)
+
+
 def test_save_load_file():
-    save_load_file(OmegaConf.create(dict(a=10)))
+    save_load_file_deprecated(OmegaConf.create(dict(a=10)))
 
 
 def test_save_load_filename():
@@ -66,6 +116,14 @@ def test_save_load__from_filename():
     save_load__from_filename(OmegaConf.create(dict(a=10)))
 
 
+def test_save_illegal_type():
+    with raises(TypeError):
+        OmegaConf.save(OmegaConf.create(), 1000)
+
+
+##############################################
+
+
 def test_pickle_dict():
     with tempfile.TemporaryFile() as fp:
         import pickle
@@ -92,8 +150,3 @@ def test_pickle_list():
 
 def test_save_load_unicode():
     save_load_filename(OmegaConf.create([u"שלום"]))
-
-
-def test_save_illegal_type():
-    with raises(TypeError):
-        OmegaConf.create().save(1000)