From 385b5b9f2394463784d5d13a9d38ba36b8f8c28c Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sat, 30 Mar 2024 17:34:29 +0800 Subject: [PATCH 01/31] replace `raise` for warning with `warn` explicitly check for None fix string format in `test_io` add missing f for format string fix date format fix datetime in string DEBUG: comment out no warn case 3 DEBUG: comment out no warn case 2 DEBUG: comment out no warn case try to fix warning fix time patch try to fix date patch revert to monkeypatch --- monty/dev.py | 16 +++++++++------- tests/test_dev.py | 9 +++------ tests/test_io.py | 8 ++------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 359933072..fccfc0105 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -72,12 +72,13 @@ def _is_in_owner_repo() -> bool: # Only raise warning in code owner's repo CI if ( _deadline is not None - and os.getenv("CI") + and os.getenv("CI") is not None and datetime.now() > _deadline and _is_in_owner_repo() ): - raise DeprecationWarning( - "This function should have been removed on {deadline:%Y-%m-%d}." + warnings.warn( + f"This function should have been removed on {_deadline:%Y-%m-%d}.", + DeprecationWarning, ) def craft_message( @@ -89,7 +90,7 @@ def craft_message( msg = f"{old.__name__} is deprecated" if deadline is not None: - msg += f", and will be removed on {deadline:%Y-%m-%d}\n" + msg += f", and will be removed on {_deadline:%Y-%m-%d}\n" if replacement is not None: if isinstance(replacement, property): @@ -108,6 +109,10 @@ def deprecated_decorator(old: Callable): def wrapped(*args, **kwargs): msg = craft_message(old, replacement, message, _deadline) warnings.warn(msg, category=category, stacklevel=2) + + # Raise a CI warning after removal deadline + raise_deadline_warning() + return old(*args, **kwargs) return wrapped @@ -115,9 +120,6 @@ def wrapped(*args, **kwargs): # Convert deadline to datetime type _deadline = datetime(*deadline) if deadline is not None else None - # Raise a CI warning after removal deadline - raise_deadline_warning() - return deprecated_decorator diff --git a/tests/test_dev.py b/tests/test_dev.py index 6b9816a7d..610c089b7 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -94,9 +94,8 @@ def func_old(): pass with warnings.catch_warnings(record=True) as warn_msgs: - # Trigger a warning. - func_old() - # Verify message + func_old() # trigger a warning + assert "will be removed on 2000-01-01" in str(warn_msgs[0].message) def test_deprecated_deadline_no_warn(self, monkeypatch): @@ -108,9 +107,7 @@ def func_old(): # No warn case 1: date before deadline with warnings.catch_warnings(record=True) as warn_msgs: - monkeypatch.setattr( - datetime, "datetime", lambda: datetime.datetime(1999, 1, 1) - ) + monkeypatch.setattr(datetime, "datetime", datetime.datetime(1999, 1, 1)) func_old() for warning in warn_msgs: diff --git a/tests/test_io.py b/tests/test_io.py index 043e67440..2aa676111 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -32,9 +32,7 @@ def test_reverse_readline(self): for idx, line in enumerate(reverse_readline(f)): assert ( int(line) == self.NUMLINES - idx - ), "read_backwards read {} whereas it should "( - "have read {" "}" - ).format(int(line), self.NUMLINES - idx) + ), f"read_backwards read {int(line)} whereas it should have read {self.NUMLINES - idx}" def test_reverse_readline_fake_big(self): """ @@ -44,9 +42,7 @@ def test_reverse_readline_fake_big(self): for idx, line in enumerate(reverse_readline(f, max_mem=0)): assert ( int(line) == self.NUMLINES - idx - ), "read_backwards read {} whereas it should "( - "have read {" "}" - ).format(int(line), self.NUMLINES - idx) + ), f"read_backwards read {int(line)} whereas it should have read {self.NUMLINES - idx}" def test_reverse_readline_bz2(self): """ From 49d51de9e2ea01bbd99842add09e7ea5ad939b60 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 16:43:58 +0800 Subject: [PATCH 02/31] adjust warn position --- monty/dev.py | 7 +++---- tests/test_dev.py | 41 +++++++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index fccfc0105..6b0c22d76 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -109,10 +109,6 @@ def deprecated_decorator(old: Callable): def wrapped(*args, **kwargs): msg = craft_message(old, replacement, message, _deadline) warnings.warn(msg, category=category, stacklevel=2) - - # Raise a CI warning after removal deadline - raise_deadline_warning() - return old(*args, **kwargs) return wrapped @@ -120,6 +116,9 @@ def wrapped(*args, **kwargs): # Convert deadline to datetime type _deadline = datetime(*deadline) if deadline is not None else None + # Raise CI warning after removal deadline + raise_deadline_warning() + return deprecated_decorator diff --git a/tests/test_dev.py b/tests/test_dev.py index 610c089b7..e0db969e4 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -89,36 +89,38 @@ def classmethod_b(cls): assert TestClass_deprecationwarning().classmethod_b() == "b" def test_deprecated_deadline(self): - @deprecated(deadline=(2000, 1, 1)) - def func_old(): - pass with warnings.catch_warnings(record=True) as warn_msgs: - func_old() # trigger a warning + @deprecated(deadline=(2000, 1, 1)) + def func_old(): + pass - assert "will be removed on 2000-01-01" in str(warn_msgs[0].message) + assert "This function should have been removed" in str(warn_msgs[0].message) def test_deprecated_deadline_no_warn(self, monkeypatch): """Test cases where no warning should be raised.""" - @deprecated(deadline=(2000, 1, 1)) - def func_old(): - pass + # # No warn case 1: date before deadline + # DEBUG + # with warnings.catch_warnings(record=True) as warn_msgs: + # monkeypatch.setattr(datetime.datetime, "now", datetime.datetime(1999, 1, 1)) - # No warn case 1: date before deadline - with warnings.catch_warnings(record=True) as warn_msgs: - monkeypatch.setattr(datetime, "datetime", datetime.datetime(1999, 1, 1)) - func_old() + # @deprecated(deadline=(2000, 1, 1)) + # def func_old(): + # pass - for warning in warn_msgs: - assert "This function should have been removed on" not in str( - warning.message - ) + # for warning in warn_msgs: + # assert "This function should have been removed on" not in str( + # warning.message + # ) # No warn case 2: not in CI env with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.delenv("CI", raising=False) - func_old() + + @deprecated(deadline=(2000, 1, 1)) + def func_old(): + pass for warning in warn_msgs: assert "This function should have been removed on" not in str( @@ -128,7 +130,10 @@ def func_old(): # No warn case 3: not in code owner repo with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.setenv("GITHUB_REPOSITORY", "NONE/NONE") - func_old() + + @deprecated(deadline=(2000, 1, 1)) + def func_old(): + pass for warning in warn_msgs: assert "This function should have been removed on" not in str( From 12a646d602e8f078212a9ccb6970253c111bd201 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 31 Mar 2024 08:44:11 +0000 Subject: [PATCH 03/31] pre-commit auto-fixes --- tests/test_dev.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index e0db969e4..f036b76a9 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,6 +1,5 @@ import unittest import warnings -import datetime import pytest from monty.dev import deprecated, install_excepthook, requires @@ -89,8 +88,8 @@ def classmethod_b(cls): assert TestClass_deprecationwarning().classmethod_b() == "b" def test_deprecated_deadline(self): - with warnings.catch_warnings(record=True) as warn_msgs: + @deprecated(deadline=(2000, 1, 1)) def func_old(): pass From 1998adb90c7b99e43b9b1ae706a03f5dc4996c98 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:08:22 +0800 Subject: [PATCH 04/31] fix datetime.now patch --- tests/test_dev.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index e0db969e4..0b366f89d 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,6 +1,7 @@ import unittest import warnings import datetime +from unittest.mock import MagicMock import pytest from monty.dev import deprecated, install_excepthook, requires @@ -88,9 +89,12 @@ def classmethod_b(cls): with pytest.warns(DeprecationWarning): assert TestClass_deprecationwarning().classmethod_b() == "b" - def test_deprecated_deadline(self): - + @pytest.mark.skip() + def test_deprecated_deadline(self, monkeypatch): with warnings.catch_warnings(record=True) as warn_msgs: + monkeypatch.setenv("CI", "true") + monkeypatch.setenv("GITHUB_REPOSITORY", "materialsvirtuallab/monty") + @deprecated(deadline=(2000, 1, 1)) def func_old(): pass @@ -100,26 +104,28 @@ def func_old(): def test_deprecated_deadline_no_warn(self, monkeypatch): """Test cases where no warning should be raised.""" - # # No warn case 1: date before deadline - # DEBUG - # with warnings.catch_warnings(record=True) as warn_msgs: - # monkeypatch.setattr(datetime.datetime, "now", datetime.datetime(1999, 1, 1)) + # No warn case 1: date before deadline + with warnings.catch_warnings(record=True) as warn_msgs: + # Mock date to 1999-01-01 + datetime_mock = MagicMock(wrap=datetime.datetime) + datetime_mock.now.return_value = datetime.datetime(1999, 1, 1) + monkeypatch.setattr(datetime, "datetime", datetime_mock) - # @deprecated(deadline=(2000, 1, 1)) - # def func_old(): - # pass + @deprecated(deadline=(2000, 1, 1)) + def func_old_0(): + pass - # for warning in warn_msgs: - # assert "This function should have been removed on" not in str( - # warning.message - # ) + for warning in warn_msgs: + assert "This function should have been removed on" not in str( + warning.message + ) # No warn case 2: not in CI env with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.delenv("CI", raising=False) @deprecated(deadline=(2000, 1, 1)) - def func_old(): + def func_old_1(): pass for warning in warn_msgs: @@ -132,7 +138,7 @@ def func_old(): monkeypatch.setenv("GITHUB_REPOSITORY", "NONE/NONE") @deprecated(deadline=(2000, 1, 1)) - def func_old(): + def func_old_2(): pass for warning in warn_msgs: From 07d52f3274fc686566dc52626f22ade02e66751b Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:11:15 +0800 Subject: [PATCH 05/31] revert to `raise` for `DeprecationWarning` --- monty/dev.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 6b0c22d76..241b912ef 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -76,9 +76,8 @@ def _is_in_owner_repo() -> bool: and datetime.now() > _deadline and _is_in_owner_repo() ): - warnings.warn( - f"This function should have been removed on {_deadline:%Y-%m-%d}.", - DeprecationWarning, + raise DeprecationWarning( + f"This function should have been removed on {_deadline:%Y-%m-%d}." ) def craft_message( From 890ed14f7b155d49c572e1b18d7bea0184354877 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:31:31 +0800 Subject: [PATCH 06/31] temp fix for "should raise" case --- tests/test_dev.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 0b366f89d..903269321 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -89,9 +89,8 @@ def classmethod_b(cls): with pytest.warns(DeprecationWarning): assert TestClass_deprecationwarning().classmethod_b() == "b" - @pytest.mark.skip() def test_deprecated_deadline(self, monkeypatch): - with warnings.catch_warnings(record=True) as warn_msgs: + with pytest.raises(DeprecationWarning): monkeypatch.setenv("CI", "true") monkeypatch.setenv("GITHUB_REPOSITORY", "materialsvirtuallab/monty") @@ -99,13 +98,11 @@ def test_deprecated_deadline(self, monkeypatch): def func_old(): pass - assert "This function should have been removed" in str(warn_msgs[0].message) - def test_deprecated_deadline_no_warn(self, monkeypatch): """Test cases where no warning should be raised.""" # No warn case 1: date before deadline - with warnings.catch_warnings(record=True) as warn_msgs: + with warnings.catch_warnings(): # Mock date to 1999-01-01 datetime_mock = MagicMock(wrap=datetime.datetime) datetime_mock.now.return_value = datetime.datetime(1999, 1, 1) @@ -115,37 +112,22 @@ def test_deprecated_deadline_no_warn(self, monkeypatch): def func_old_0(): pass - for warning in warn_msgs: - assert "This function should have been removed on" not in str( - warning.message - ) - # No warn case 2: not in CI env - with warnings.catch_warnings(record=True) as warn_msgs: + with warnings.catch_warnings(): monkeypatch.delenv("CI", raising=False) @deprecated(deadline=(2000, 1, 1)) def func_old_1(): pass - for warning in warn_msgs: - assert "This function should have been removed on" not in str( - warning.message - ) - # No warn case 3: not in code owner repo - with warnings.catch_warnings(record=True) as warn_msgs: - monkeypatch.setenv("GITHUB_REPOSITORY", "NONE/NONE") + with warnings.catch_warnings(): + monkeypatch.delenv("GITHUB_REPOSITORY", raising=False) @deprecated(deadline=(2000, 1, 1)) def func_old_2(): pass - for warning in warn_msgs: - assert "This function should have been removed on" not in str( - warning.message - ) - def test_requires(self): try: import fictitious_mod # type: ignore From 4ea8d1bb7db1cf0f29f0fa0f097d686eb6a97638 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:36:50 +0800 Subject: [PATCH 07/31] add `ipython` to optional requirements --- requirements-optional.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-optional.txt b/requirements-optional.txt index f94a7fa4b..ccdfc7d6c 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -7,3 +7,4 @@ pandas==2.2.1 orjson==3.9.15 types-orjson==3.6.2 types-requests==2.31.0.20240218 +ipython==8.22.2 From 5da8026c9bce39db6bf90cdbd14c78f64c0ea7e6 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:43:02 +0800 Subject: [PATCH 08/31] remove accidentially commited files --- tests/test_files/3000_lines.txt.gz | Bin 6496 -> 0 bytes venv/pyvenv.cfg | 3 -- venv/share/man/man1/ipython.1 | 60 ----------------------------- 3 files changed, 63 deletions(-) delete mode 100644 tests/test_files/3000_lines.txt.gz delete mode 100644 venv/pyvenv.cfg delete mode 100644 venv/share/man/man1/ipython.1 diff --git a/tests/test_files/3000_lines.txt.gz b/tests/test_files/3000_lines.txt.gz deleted file mode 100644 index e58423003e61f5de71db9505a3de26f97aa2a991..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6496 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 Sb90Atg?^RZT>@;cqVhk8G>U%! diff --git a/venv/pyvenv.cfg b/venv/pyvenv.cfg deleted file mode 100644 index 0537ffc00..000000000 --- a/venv/pyvenv.cfg +++ /dev/null @@ -1,3 +0,0 @@ -home = /usr/bin -include-system-site-packages = false -version = 3.10.12 diff --git a/venv/share/man/man1/ipython.1 b/venv/share/man/man1/ipython.1 deleted file mode 100644 index 0f4a191f3..000000000 --- a/venv/share/man/man1/ipython.1 +++ /dev/null @@ -1,60 +0,0 @@ -.\" Hey, EMACS: -*- nroff -*- -.\" First parameter, NAME, should be all caps -.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection -.\" other parameters are allowed: see man(7), man(1) -.TH IPYTHON 1 "July 15, 2011" -.\" Please adjust this date whenever revising the manpage. -.\" -.\" Some roff macros, for reference: -.\" .nh disable hyphenation -.\" .hy enable hyphenation -.\" .ad l left justify -.\" .ad b justify to both left and right margins -.\" .nf disable filling -.\" .fi enable filling -.\" .br insert line break -.\" .sp insert n+1 empty lines -.\" for manpage-specific macros, see man(7) and groff_man(7) -.\" .SH section heading -.\" .SS secondary section heading -.\" -.\" -.\" To preview this page as plain text: nroff -man ipython.1 -.\" -.SH NAME -ipython \- Tools for Interactive Computing in Python. -.SH SYNOPSIS -.B ipython -.RI [ options ] " files" ... - -.B ipython subcommand -.RI [ options ] ... - -.SH DESCRIPTION -An interactive Python shell with automatic history (input and output), dynamic -object introspection, easier configuration, command completion, access to the -system shell, integration with numerical and scientific computing tools, -web notebook, Qt console, and more. - -For more information on how to use IPython, see 'ipython \-\-help', -or 'ipython \-\-help\-all' for all available command\(hyline options. - -.SH "ENVIRONMENT VARIABLES" -.sp -.PP -\fIIPYTHONDIR\fR -.RS 4 -This is the location where IPython stores all its configuration files. The default -is $HOME/.ipython if IPYTHONDIR is not defined. - -You can see the computed value of IPYTHONDIR with `ipython locate`. - -.SH FILES - -IPython uses various configuration files stored in profiles within IPYTHONDIR. -To generate the default configuration files and start configuring IPython, -do 'ipython profile create', and edit '*_config.py' files located in -IPYTHONDIR/profile_default. - -.SH AUTHORS -IPython is written by the IPython Development Team . From d69055a080a093b493b014b5c224a1a70b51f880 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Sun, 31 Mar 2024 20:45:27 +0800 Subject: [PATCH 09/31] add test file back --- tests/test_files/3000_lines.txt.gz | Bin 0 -> 6496 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_files/3000_lines.txt.gz diff --git a/tests/test_files/3000_lines.txt.gz b/tests/test_files/3000_lines.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..25cd7bac5914729c80453443492cf45dfb765bf1 GIT binary patch literal 6496 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 TI5u}Umg`pP+$F%aN=p9&j020V literal 0 HcmV?d00001 From bcc50019cd101458f0dc211d70a1e1c143b1e700 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 1 Apr 2024 10:30:13 +0800 Subject: [PATCH 10/31] mock GITHUB_REPOSITORY --- tests/test_dev.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 903269321..fa84f9555 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,7 +1,7 @@ import unittest import warnings import datetime -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest from monty.dev import deprecated, install_excepthook, requires @@ -91,12 +91,18 @@ def classmethod_b(cls): def test_deprecated_deadline(self, monkeypatch): with pytest.raises(DeprecationWarning): - monkeypatch.setenv("CI", "true") - monkeypatch.setenv("GITHUB_REPOSITORY", "materialsvirtuallab/monty") - - @deprecated(deadline=(2000, 1, 1)) - def func_old(): - pass + monkeypatch.setenv("CI", "true") # mock CI env + + with patch("subprocess.run") as mock_run: + # Mock "GITHUB_REPOSITORY" + monkeypatch.setenv("GITHUB_REPOSITORY", "TESTOWNER/TESTREPO") + mock_run.return_value.stdout.decode.return_value = ( + "git@github.com:TESTOWNER/TESTREPO.git" + ) + + @deprecated(deadline=(2000, 1, 1)) + def func_old(): + pass def test_deprecated_deadline_no_warn(self, monkeypatch): """Test cases where no warning should be raised.""" From fadaf1461da1b6d3ba5d5f39a37df582a0471fe9 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 1 Apr 2024 10:47:44 +0800 Subject: [PATCH 11/31] refine unit test for deadline --- tests/test_dev.py | 50 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index fa84f9555..8a13c0123 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,7 +1,7 @@ import unittest import warnings import datetime -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest from monty.dev import deprecated, install_excepthook, requires @@ -91,9 +91,9 @@ def classmethod_b(cls): def test_deprecated_deadline(self, monkeypatch): with pytest.raises(DeprecationWarning): - monkeypatch.setenv("CI", "true") # mock CI env - with patch("subprocess.run") as mock_run: + monkeypatch.setenv("CI", "true") # mock CI env + # Mock "GITHUB_REPOSITORY" monkeypatch.setenv("GITHUB_REPOSITORY", "TESTOWNER/TESTREPO") mock_run.return_value.stdout.decode.return_value = ( @@ -104,30 +104,52 @@ def test_deprecated_deadline(self, monkeypatch): def func_old(): pass + @pytest.fixture() def test_deprecated_deadline_no_warn(self, monkeypatch): """Test cases where no warning should be raised.""" # No warn case 1: date before deadline with warnings.catch_warnings(): - # Mock date to 1999-01-01 - datetime_mock = MagicMock(wrap=datetime.datetime) - datetime_mock.now.return_value = datetime.datetime(1999, 1, 1) - monkeypatch.setattr(datetime, "datetime", datetime_mock) + with patch("subprocess.run") as mock_run: + monkeypatch.setenv("CI", "true") # mock CI env - @deprecated(deadline=(2000, 1, 1)) - def func_old_0(): - pass + # Mock date to 1999-01-01 + monkeypatch.setattr( + datetime.datetime, "now", datetime.datetime(1999, 1, 1) + ) + + # Mock "GITHUB_REPOSITORY" + monkeypatch.setenv("GITHUB_REPOSITORY", "TESTOWNER/TESTREPO") + mock_run.return_value.stdout.decode.return_value = ( + "git@github.com:TESTOWNER/TESTREPO.git" + ) + + @deprecated(deadline=(2000, 1, 1)) + def func_old(): + pass + + monkeypatch.undo() # No warn case 2: not in CI env with warnings.catch_warnings(): - monkeypatch.delenv("CI", raising=False) + with patch("subprocess.run") as mock_run: + monkeypatch.delenv("CI", raising=False) - @deprecated(deadline=(2000, 1, 1)) - def func_old_1(): - pass + # Mock "GITHUB_REPOSITORY" + monkeypatch.setenv("GITHUB_REPOSITORY", "TESTOWNER/TESTREPO") + mock_run.return_value.stdout.decode.return_value = ( + "git@github.com:TESTOWNER/TESTREPO.git" + ) + + @deprecated(deadline=(2000, 1, 1)) + def func_old_1(): + pass + + monkeypatch.undo() # No warn case 3: not in code owner repo with warnings.catch_warnings(): + monkeypatch.setenv("CI", "true") monkeypatch.delenv("GITHUB_REPOSITORY", raising=False) @deprecated(deadline=(2000, 1, 1)) From 5e589095d30557cfab19c99bb9952a610c9769ee Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 1 Apr 2024 11:05:39 +0800 Subject: [PATCH 12/31] remove `unittest.main()` --- tests/test_tempfile.py | 5 ----- tests/test_termcolor.py | 5 ----- 2 files changed, 10 deletions(-) diff --git a/tests/test_tempfile.py b/tests/test_tempfile.py index ac57a20df..80372f3b6 100644 --- a/tests/test_tempfile.py +++ b/tests/test_tempfile.py @@ -1,6 +1,5 @@ import os import shutil -import unittest from monty.tempfile import ScratchDir @@ -142,7 +141,3 @@ def test_bad_root(self): def teardown_method(self): os.chdir(self.cwd) shutil.rmtree(self.scratch_root) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_termcolor.py b/tests/test_termcolor.py index a57f0c55c..843e25442 100644 --- a/tests/test_termcolor.py +++ b/tests/test_termcolor.py @@ -4,7 +4,6 @@ import os import sys -import unittest from monty.termcolor import ( cprint, @@ -75,7 +74,3 @@ def test_remove_non_ascii(self): def test_stream_has_colors(self): # TODO: not a real test. Need to do a proper test. stream_has_colours(sys.stdout) - - -if __name__ == "__main__": - unittest.main() From 4c2f131d059e168ccb5303923b8227c638aef870 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:26:57 +0000 Subject: [PATCH 13/31] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.4 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.3.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abd85237d..4d3d21fc3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.3.5 hooks: - id: ruff args: [--fix] From fb7caf2b45c3adfb46ee12a923d0317cff8e19b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:45:20 +0000 Subject: [PATCH 14/31] Bump types-requests from 2.31.0.20240311 to 2.31.0.20240403 Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240311 to 2.31.0.20240403. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index e30ef1737..14373652c 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -6,4 +6,4 @@ pymongo==4.6.3 pandas==2.2.1 orjson==3.10.0 types-orjson==3.6.2 -types-requests==2.31.0.20240311 +types-requests==2.31.0.20240403 From a73d4833706624796b1920302ca4455d254007e6 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 13:03:59 -0400 Subject: [PATCH 15/31] Fix simple example in docstring --- monty/json.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monty/json.py b/monty/json.py index e82ebf68c..dda4be602 100644 --- a/monty/json.py +++ b/monty/json.py @@ -139,12 +139,12 @@ class MSONable: class MSONClass(MSONable): - def __init__(self, a, b, c, d=1, **kwargs): - self.a = a - self.b = b - self._c = c - self._d = d - self.kwargs = kwargs + def __init__(self, a, b, c, d=1, **kwargs): + self.a = a + self.b = b + self._c = c + self._d = d + self.kwargs = kwargs For such classes, you merely need to inherit from MSONable and you do not need to implement your own as_dict or from_dict protocol. From f344d5c1f70a737575aa4f8230fc445675f4eb36 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:00:39 -0400 Subject: [PATCH 16/31] First attempt at modified MontyEncoder --- monty/json.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/monty/json.py b/monty/json.py index dda4be602..a940aee11 100644 --- a/monty/json.py +++ b/monty/json.py @@ -6,6 +6,7 @@ import json import os import pathlib +import pickle import traceback import types from collections import OrderedDict, defaultdict @@ -14,7 +15,7 @@ from importlib import import_module from inspect import getfullargspec from pathlib import Path -from uuid import UUID +from uuid import UUID, uuid4 try: import numpy as np @@ -241,6 +242,60 @@ def to_json(self) -> str: """ return json.dumps(self, cls=MontyEncoder) + def save( + self, + save_dir, + mkdir=True, + pickle_kwargs=None, + json_kwargs=None, + return_results=False, + ): + """Utility that uses the standard tools of MSONable to convert the + class to json format, but also save it to disk. In addition, this + method intelligently uses pickle to individually pickle class objects + that are not serializable, saving them separately. This maximizes the + readability of the saved class information while allowing _any_ + class to be at least partially serializable to disk. + + For a fully MSONable class, only a class.json file will be saved to + the location {save_dir}/class.json. For a partially MSONable class, + additional information will be saved to the save directory at + {save_dir}. This includes a pickled object for each attribute that + e serialized. + + Parameters + ---------- + save_dir : os.PathLike + The directory to which to save the class information. + mkdir : bool + If True, makes the provided directory, including all parent + directories. + """ + + save_dir = Path(save_dir) + if mkdir: + save_dir.mkdir(exist_ok=True, parents=True) + + encoder = MontyEncoder() + encoder._track_unserializable_objects = True + encoded = encoder.encode(self) + + if pickle_kwargs is None: + pickle_kwargs = {} + if json_kwargs is None: + json_kwargs = {} + + with open(save_dir / "class.json") as outfile: + json.dump(encoded, outfile, **json_kwargs) + pickle.dump( + encoded._name_object_map, + open(save_dir / "class.pkl", "wb"), + **pickle_kwargs, + ) + + if return_results: + return encoded, encoder._name_object_map + def unsafe_hash(self): """ Returns an hash of the current object. This uses a generic but low @@ -365,6 +420,10 @@ class MontyEncoder(json.JSONEncoder): json.dumps(object, cls=MontyEncoder) """ + _track_unserializable_objects = False + _name_object_map = {} + _index = 0 + def default(self, o) -> dict: # pylint: disable=E0202 """ Overriding default method for JSON encoding. This method does two @@ -466,7 +525,16 @@ def default(self, o) -> dict: # pylint: disable=E0202 d["@version"] = None # type: ignore return d except AttributeError: - return json.JSONEncoder.default(self, o) + if self._track_unserializable_objects: + # Last resort logic. We keep track of some name of the object + # as a reference, and instead of the object, store that + # name, which of course is json-serializable + name = f"{self._index:012}-{str(uuid4())}" + self._index += 1 + self._name_object_map[name] = o + return {"@object_reference": name} + else: + return json.JSONEncoder.default(self, o) class MontyDecoder(json.JSONDecoder): From 6859ee39b661171c0d4175c0dcb964c0a5b3cb34 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:21:05 -0400 Subject: [PATCH 17/31] Debug first attempt at encoder --- monty/json.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/monty/json.py b/monty/json.py index a940aee11..bfadfaa4c 100644 --- a/monty/json.py +++ b/monty/json.py @@ -285,10 +285,10 @@ class to be at least partially serializable to disk. if json_kwargs is None: json_kwargs = {} - with open(save_dir / "class.json") as outfile: + with open(save_dir / "class.json", "w") as outfile: json.dump(encoded, outfile, **json_kwargs) pickle.dump( - encoded._name_object_map, + encoder._name_object_map, open(save_dir / "class.pkl", "wb"), **pickle_kwargs, ) @@ -507,6 +507,14 @@ def default(self, o) -> dict: # pylint: disable=E0202 d = o.as_dict() elif isinstance(o, Enum): d = {"value": o.value} + elif self._track_unserializable_objects: + # Last resort logic. We keep track of some name of the object + # as a reference, and instead of the object, store that + # name, which of course is json-serializable + name = f"{self._index:012}-{str(uuid4())}" + self._index += 1 + self._name_object_map[name] = o + d = {"@object_reference": name} else: raise TypeError( f"Object of type {o.__class__.__name__} is not JSON serializable" @@ -525,16 +533,7 @@ def default(self, o) -> dict: # pylint: disable=E0202 d["@version"] = None # type: ignore return d except AttributeError: - if self._track_unserializable_objects: - # Last resort logic. We keep track of some name of the object - # as a reference, and instead of the object, store that - # name, which of course is json-serializable - name = f"{self._index:012}-{str(uuid4())}" - self._index += 1 - self._name_object_map[name] = o - return {"@object_reference": name} - else: - return json.JSONEncoder.default(self, o) + return json.JSONEncoder.default(self, o) class MontyDecoder(json.JSONDecoder): From dfbbbdff23e21c4bea2576560649729528b4a6f0 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:22:57 -0400 Subject: [PATCH 18/31] Add additional docs to save --- monty/json.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/monty/json.py b/monty/json.py index bfadfaa4c..4ebd91a99 100644 --- a/monty/json.py +++ b/monty/json.py @@ -270,6 +270,18 @@ class to be at least partially serializable to disk. mkdir : bool If True, makes the provided directory, including all parent directories. + pickle_kwargs : dict + Keyword arguments to pass to pickle.dump. + json_kwargs : dict + Keyword arguments to pass to json.dump. + return_results : bool + If true, also returns the dictionary to save to disk, as well + as the mapping between the object_references and the objects + themselves. + + Returns + ------- + None or tuple """ save_dir = Path(save_dir) From 0256dd9c66993dacb9e27ed08ac0753ae04bb78e Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:26:00 -0400 Subject: [PATCH 19/31] Add strict argument to save --- monty/json.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/monty/json.py b/monty/json.py index 4ebd91a99..88d77d1d7 100644 --- a/monty/json.py +++ b/monty/json.py @@ -249,6 +249,7 @@ def save( pickle_kwargs=None, json_kwargs=None, return_results=False, + strict=True, ): """Utility that uses the standard tools of MSONable to convert the class to json format, but also save it to disk. In addition, this @@ -275,9 +276,11 @@ class to be at least partially serializable to disk. json_kwargs : dict Keyword arguments to pass to json.dump. return_results : bool - If true, also returns the dictionary to save to disk, as well + If True, also returns the dictionary to save to disk, as well as the mapping between the object_references and the objects themselves. + strict : bool + If True, will not allow you to overwrite existing files. Returns ------- @@ -297,11 +300,18 @@ class to be at least partially serializable to disk. if json_kwargs is None: json_kwargs = {} - with open(save_dir / "class.json", "w") as outfile: + json_path = save_dir / "class.json" + pickle_path = save_dir / "class.pkl" + if strict and json_path.exists(): + raise FileExistsError(f"strict is true and file {json_path} exists") + if strict and pickle_path.exists(): + raise FileExistsError(f"strict is true and file {pickle_path} exists") + + with open(json_path, "w") as outfile: json.dump(encoded, outfile, **json_kwargs) pickle.dump( encoder._name_object_map, - open(save_dir / "class.pkl", "wb"), + open(pickle_path, "wb"), **pickle_kwargs, ) From 4726f1a8ae4ada62185664f8e4d0102f08c0b875 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:45:11 -0400 Subject: [PATCH 20/31] Add load scaffold --- monty/json.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/monty/json.py b/monty/json.py index 88d77d1d7..086da43a8 100644 --- a/monty/json.py +++ b/monty/json.py @@ -318,6 +318,19 @@ class to be at least partially serializable to disk. if return_results: return encoded, encoder._name_object_map + @classmethod + def load(cls, load_dir): + load_dir = Path(load_dir) + + json_path = load_dir / "class.json" + pickle_path = load_dir / "class.pkl" + + with open(json_path, "r") as infile: + d = json.load(infile) + name_object_map = pickle.load(open(pickle_path, "rb")) + klass = cls.from_dict(d) + return klass + def unsafe_hash(self): """ Returns an hash of the current object. This uses a generic but low From 816b65be09f1a0e93743bcd172631c2b70a4505e Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 15:45:23 -0400 Subject: [PATCH 21/31] Add initial tests for save and load --- tests/test_json.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/test_json.py b/tests/test_json.py index 44c83c42b..9cfe14dc9 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -57,6 +57,29 @@ def __eq__(self, other): ) +class GoodNOTMSONClass: + """Literally the same as the GoodMSONClass, except it does not have + the MSONable inheritance!""" + + def __init__(self, a, b, c, d=1, *values, **kwargs): + self.a = a + self.b = b + self._c = c + self._d = d + self.values = values + self.kwargs = kwargs + + def __eq__(self, other): + return ( + self.a == other.a + and self.b == other.b + and self._c == other._c + and self._d == other._d + and self.kwargs == other.kwargs + and self.values == other.values + ) + + class LimitedMSONClass(MSONable): """An MSONable class that only accepts a limited number of options""" @@ -367,6 +390,36 @@ def test_enum_serialization_no_msonable(self): f = jsanitize(d, enum_values=True) assert f["123"] == "value_a" + def test_save_load(self, tmp_path): + """Tests the save and load serialization methods.""" + + test_good_class = GoodMSONClass( + "Hello", + "World", + "Python", + cant_serialize_me=GoodNOTMSONClass("Hello2", "World2", "Python2"), + ) + + # This will pass + test_good_class.as_dict() + + # This will fail + with pytest.raises(TypeError): + test_good_class.to_json() + + # This should also pass though + target = tmp_path / "test_dir123" + test_good_class.save(target) + + # This will fail + with pytest.raises(FileExistsError): + test_good_class.save(target, strict=True) + + # Now check that reloading this, the classes are equal! + test_good_class2 = GoodMSONClass.load(target) + + assert test_good_class == test_good_class2 + class TestJson: def test_as_from_dict(self): From 1db3006e05bfdd60aefa699b2e8fde1d65130684 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Thu, 4 Apr 2024 16:44:38 -0400 Subject: [PATCH 22/31] Finalize first draft of load and tests --- monty/json.py | 27 +++++++++++++++++++-------- tests/test_json.py | 12 +++++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/monty/json.py b/monty/json.py index 086da43a8..0ae4ff7b6 100644 --- a/monty/json.py +++ b/monty/json.py @@ -223,17 +223,22 @@ def recursive_as_dict(obj): d.update({"value": self.value}) # pylint: disable=E1101 return d + @staticmethod + def decoded_from_dict(d, name_object_map): + decoder = MontyDecoder() + decoder._name_object_map = name_object_map + decoded = { + k: decoder.process_decoded(v) for k, v in d.items() if not k.startswith("@") + } + return decoded + @classmethod def from_dict(cls, d): """ :param d: Dict representation. :return: MSONable class. """ - decoded = { - k: MontyDecoder().process_decoded(v) - for k, v in d.items() - if not k.startswith("@") - } + decoded = MSONable.decoded_from_dict(d, name_object_map=None) return cls(**decoded) def to_json(self) -> str: @@ -326,9 +331,10 @@ def load(cls, load_dir): pickle_path = load_dir / "class.pkl" with open(json_path, "r") as infile: - d = json.load(infile) + d = json.loads(json.load(infile)) name_object_map = pickle.load(open(pickle_path, "rb")) - klass = cls.from_dict(d) + decoded = MSONable.decoded_from_dict(d, name_object_map) + klass = cls(**decoded) return klass def unsafe_hash(self): @@ -586,13 +592,18 @@ class MontyDecoder(json.JSONDecoder): json.loads(json_string, cls=MontyDecoder) """ + _name_object_map = None + def process_decoded(self, d): """ Recursive method to support decoding dicts and lists containing pymatgen objects. """ if isinstance(d, dict): - if "@module" in d and "@class" in d: + if "@object_reference" in d and self._name_object_map is not None: + name = d["@object_reference"] + return self._name_object_map.pop(name) + elif "@module" in d and "@class" in d: modname = d["@module"] classname = d["@class"] if cls_redirect := MSONable.REDIRECT.get(modname, {}).get(classname): diff --git a/tests/test_json.py b/tests/test_json.py index 9cfe14dc9..6282935d5 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -397,7 +397,14 @@ def test_save_load(self, tmp_path): "Hello", "World", "Python", - cant_serialize_me=GoodNOTMSONClass("Hello2", "World2", "Python2"), + **{ + "cant_serialize_me": GoodNOTMSONClass( + "Hello2", + "World2", + "Python2", + ), + "values": [], + }, ) # This will pass @@ -417,8 +424,7 @@ def test_save_load(self, tmp_path): # Now check that reloading this, the classes are equal! test_good_class2 = GoodMSONClass.load(target) - - assert test_good_class == test_good_class2 + assert test_good_class, test_good_class2 class TestJson: From 3ce9e60b0baab63ef62f3afc112d6ebbf4a810f1 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 11:41:47 -0400 Subject: [PATCH 23/31] Address mypy issue, add load documentation --- monty/json.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/monty/json.py b/monty/json.py index 0ae4ff7b6..cfee0598d 100644 --- a/monty/json.py +++ b/monty/json.py @@ -15,6 +15,7 @@ from importlib import import_module from inspect import getfullargspec from pathlib import Path +from typing import Any, Dict from uuid import UUID, uuid4 try: @@ -325,6 +326,20 @@ class to be at least partially serializable to disk. @classmethod def load(cls, load_dir): + """Loads a class from a provided {load_dir}/class.json and + {load_dir}/class.pkl file (if necessary). + + Parameters + ---------- + load_dir : os.PathLike + The directory from which to reload the class from. + + Returns + ------- + MSONable + An instance of the class being reloaded. + """ + load_dir = Path(load_dir) json_path = load_dir / "class.json" @@ -462,7 +477,7 @@ class MontyEncoder(json.JSONEncoder): """ _track_unserializable_objects = False - _name_object_map = {} + _name_object_map: Dict[str, Any] = {} _index = 0 def default(self, o) -> dict: # pylint: disable=E0202 From efec55eca63d6cee683d79737355a806176a66fe Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 12:41:39 -0400 Subject: [PATCH 24/31] Update MontyEncoder to handle edge case Sometimes, the object to be serialized is callable, not MSONable, but also lacks the `__name__` attribute. Add fallback for if an `AttributeError` is thrown when attempting to perform this serialization. --- monty/json.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/monty/json.py b/monty/json.py index cfee0598d..7783882fe 100644 --- a/monty/json.py +++ b/monty/json.py @@ -480,6 +480,12 @@ class MontyEncoder(json.JSONEncoder): _name_object_map: Dict[str, Any] = {} _index = 0 + def _update_name_object_map(self, o): + name = f"{self._index:012}-{str(uuid4())}" + self._index += 1 + self._name_object_map[name] = o + return {"@object_reference": name} + def default(self, o) -> dict: # pylint: disable=E0202 """ Overriding default method for JSON encoding. This method does two @@ -547,7 +553,11 @@ def default(self, o) -> dict: # pylint: disable=E0202 return {"@module": "bson.objectid", "@class": "ObjectId", "oid": str(o)} if callable(o) and not isinstance(o, MSONable): - return _serialize_callable(o) + try: + return _serialize_callable(o) + except AttributeError: + # Some callables may not have instance __name__ + return self._update_name_object_map(o) try: if pydantic is not None and isinstance(o, pydantic.BaseModel): @@ -567,10 +577,7 @@ def default(self, o) -> dict: # pylint: disable=E0202 # Last resort logic. We keep track of some name of the object # as a reference, and instead of the object, store that # name, which of course is json-serializable - name = f"{self._index:012}-{str(uuid4())}" - self._index += 1 - self._name_object_map[name] = o - d = {"@object_reference": name} + d = self._update_name_object_map(o) else: raise TypeError( f"Object of type {o.__class__.__name__} is not JSON serializable" From 5771070dd30b52f2795740764bed3443902a2f79 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 13:13:09 -0400 Subject: [PATCH 25/31] Refactor encoder and encoding procedure --- monty/json.py | 57 +++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/monty/json.py b/monty/json.py index 7783882fe..0ead07581 100644 --- a/monty/json.py +++ b/monty/json.py @@ -250,7 +250,7 @@ def to_json(self) -> str: def save( self, - save_dir, + save_dir=None, mkdir=True, pickle_kwargs=None, json_kwargs=None, @@ -280,7 +280,7 @@ class to be at least partially serializable to disk. pickle_kwargs : dict Keyword arguments to pass to pickle.dump. json_kwargs : dict - Keyword arguments to pass to json.dump. + Keyword arguments to pass to the serializer. return_results : bool If True, also returns the dictionary to save to disk, as well as the mapping between the object_references and the objects @@ -293,33 +293,34 @@ class to be at least partially serializable to disk. None or tuple """ - save_dir = Path(save_dir) - if mkdir: - save_dir.mkdir(exist_ok=True, parents=True) - - encoder = MontyEncoder() - encoder._track_unserializable_objects = True - encoded = encoder.encode(self) + if save_dir is None and not return_results: + raise ValueError("save_dir must be set and/or return_results must be True") if pickle_kwargs is None: pickle_kwargs = {} if json_kwargs is None: json_kwargs = {} + encoder = MontyEncoder(allow_unserializable_objects=True, **json_kwargs) + encoded = encoder.encode(self) - json_path = save_dir / "class.json" - pickle_path = save_dir / "class.pkl" - if strict and json_path.exists(): - raise FileExistsError(f"strict is true and file {json_path} exists") - if strict and pickle_path.exists(): - raise FileExistsError(f"strict is true and file {pickle_path} exists") - - with open(json_path, "w") as outfile: - json.dump(encoded, outfile, **json_kwargs) - pickle.dump( - encoder._name_object_map, - open(pickle_path, "wb"), - **pickle_kwargs, - ) + if save_dir is not None: + save_dir = Path(save_dir) + if mkdir: + save_dir.mkdir(exist_ok=True, parents=True) + json_path = save_dir / "class.json" + pickle_path = save_dir / "class.pkl" + if strict and json_path.exists(): + raise FileExistsError(f"strict is true and file {json_path} exists") + if strict and pickle_path.exists(): + raise FileExistsError(f"strict is true and file {pickle_path} exists") + + with open(json_path, "w") as outfile: + outfile.write(encoded) + pickle.dump( + encoder._name_object_map, + open(pickle_path, "wb"), + **pickle_kwargs, + ) if return_results: return encoded, encoder._name_object_map @@ -346,7 +347,7 @@ def load(cls, load_dir): pickle_path = load_dir / "class.pkl" with open(json_path, "r") as infile: - d = json.loads(json.load(infile)) + d = json.loads(infile.read()) name_object_map = pickle.load(open(pickle_path, "rb")) decoded = MSONable.decoded_from_dict(d, name_object_map) klass = cls(**decoded) @@ -476,9 +477,11 @@ class MontyEncoder(json.JSONEncoder): json.dumps(object, cls=MontyEncoder) """ - _track_unserializable_objects = False - _name_object_map: Dict[str, Any] = {} - _index = 0 + def __init__(self, *args, allow_unserializable_objects=False, **kwargs): + super().__init__(*args, **kwargs) + self._track_unserializable_objects = allow_unserializable_objects + self._name_object_map: Dict[str, Any] = {} + self._index = 0 def _update_name_object_map(self, o): name = f"{self._index:012}-{str(uuid4())}" From 06e4c9872b02b820b0f87988ff50dca587ad2f98 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 13:17:26 -0400 Subject: [PATCH 26/31] Handle standard case when attribute error is thrown in MontyEncoder --- monty/json.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monty/json.py b/monty/json.py index 0ead07581..c3318272b 100644 --- a/monty/json.py +++ b/monty/json.py @@ -558,9 +558,11 @@ def default(self, o) -> dict: # pylint: disable=E0202 if callable(o) and not isinstance(o, MSONable): try: return _serialize_callable(o) - except AttributeError: + except AttributeError as e: # Some callables may not have instance __name__ - return self._update_name_object_map(o) + if self._track_unserializable_objects: + return self._update_name_object_map(o) + raise AttributeError(e) try: if pydantic is not None and isinstance(o, pydantic.BaseModel): From ecffa668b2b4a0f0224c17be6f6902561ad648df Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 14:19:16 -0400 Subject: [PATCH 27/31] Improve test case for robustness --- tests/test_json.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_json.py b/tests/test_json.py index 6282935d5..c0e8b84d8 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -403,6 +403,38 @@ def test_save_load(self, tmp_path): "World2", "Python2", ), + "cant_serialize_me2": [ + GoodNOTMSONClass( + "Hello4", + "World4", + "Python4", + ), + GoodNOTMSONClass( + "Hello4", + "World4", + "Python4", + ), + ], + "cant_serialize_me3": [ + { + "tmp": GoodNOTMSONClass( + "Hello5", + "World5", + "Python5", + ), + "tmp2": 2, + "tmp3": [1, 2, 3], + }, + { + "tmp5": GoodNOTMSONClass( + "aHello5", + "aWorld5", + "aPython5", + ), + "tmp2": 5, + "tmp3": {"test": "test123"}, + }, + ], "values": [], }, ) From 51b4d13b48c4b1d0258e755b977e3626ce791bf4 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 14:53:21 -0400 Subject: [PATCH 28/31] Correct typo in tests --- tests/test_json.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_json.py b/tests/test_json.py index c0e8b84d8..f74fb1195 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -434,6 +434,9 @@ def test_save_load(self, tmp_path): "tmp2": 5, "tmp3": {"test": "test123"}, }, + # Gotta check that if I hide an MSONable class somewhere + # it still gets correctly serialized. + {"actually_good": GoodMSONClass("1", "2", "3")}, ], "values": [], }, @@ -456,7 +459,7 @@ def test_save_load(self, tmp_path): # Now check that reloading this, the classes are equal! test_good_class2 = GoodMSONClass.load(target) - assert test_good_class, test_good_class2 + assert test_good_class == test_good_class2 class TestJson: From d61973759cdc07f61fd69ad4e56e79d6e3111199 Mon Sep 17 00:00:00 2001 From: Matthew Carbone Date: Fri, 5 Apr 2024 15:25:20 -0400 Subject: [PATCH 29/31] Bugfix json.py, make tests more robust --- monty/json.py | 11 +++++++++++ tests/test_json.py | 31 +++++++++---------------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/monty/json.py b/monty/json.py index c3318272b..fbbd84ee3 100644 --- a/monty/json.py +++ b/monty/json.py @@ -233,6 +233,11 @@ def decoded_from_dict(d, name_object_map): } return decoded + @classmethod + def _from_dict(cls, d, name_object_map): + decoded = MSONable.decoded_from_dict(d, name_object_map=name_object_map) + return cls(**decoded) + @classmethod def from_dict(cls, d): """ @@ -626,6 +631,7 @@ def process_decoded(self, d): Recursive method to support decoding dicts and lists containing pymatgen objects. """ + if isinstance(d, dict): if "@object_reference" in d and self._name_object_map is not None: name = d["@object_reference"] @@ -693,6 +699,11 @@ def process_decoded(self, d): if hasattr(mod, classname): cls_ = getattr(mod, classname) data = {k: v for k, v in d.items() if not k.startswith("@")} + if hasattr(cls_, "_from_dict"): + # New functionality with save/load requires this + return cls_._from_dict( + data, name_object_map=self._name_object_map + ) if hasattr(cls_, "from_dict"): return cls_.from_dict(data) if issubclass(cls_, Enum): diff --git a/tests/test_json.py b/tests/test_json.py index f74fb1195..206c5955e 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -399,44 +399,30 @@ def test_save_load(self, tmp_path): "Python", **{ "cant_serialize_me": GoodNOTMSONClass( - "Hello2", - "World2", - "Python2", + "Hello2", "World2", "Python2", **{"values": []} ), "cant_serialize_me2": [ - GoodNOTMSONClass( - "Hello4", - "World4", - "Python4", - ), - GoodNOTMSONClass( - "Hello4", - "World4", - "Python4", - ), + GoodNOTMSONClass("Hello4", "World4", "Python4", **{"values": []}), + GoodNOTMSONClass("Hello4", "World4", "Python4", **{"values": []}), ], "cant_serialize_me3": [ { - "tmp": GoodNOTMSONClass( - "Hello5", - "World5", - "Python5", + "tmp": GoodMSONClass( + "Hello5", "World5", "Python5", **{"values": []} ), "tmp2": 2, "tmp3": [1, 2, 3], }, { "tmp5": GoodNOTMSONClass( - "aHello5", - "aWorld5", - "aPython5", + "aHello5", "aWorld5", "aPython5", **{"values": []} ), "tmp2": 5, "tmp3": {"test": "test123"}, }, # Gotta check that if I hide an MSONable class somewhere # it still gets correctly serialized. - {"actually_good": GoodMSONClass("1", "2", "3")}, + {"actually_good": GoodMSONClass("1", "2", "3", **{"values": []})}, ], "values": [], }, @@ -451,7 +437,7 @@ def test_save_load(self, tmp_path): # This should also pass though target = tmp_path / "test_dir123" - test_good_class.save(target) + test_good_class.save(target, json_kwargs={"indent": 4, "sort_keys": True}) # This will fail with pytest.raises(FileExistsError): @@ -459,6 +445,7 @@ def test_save_load(self, tmp_path): # Now check that reloading this, the classes are equal! test_good_class2 = GoodMSONClass.load(target) + assert test_good_class == test_good_class2 From 042df49a0487e5e16f60ea358b63d3db69fda04c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:13:06 +0000 Subject: [PATCH 30/31] Bump pandas from 2.2.1 to 2.2.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.1...v2.2.2) --- updated-dependencies: - dependency-name: pandas dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index 14373652c..997c0b896 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -3,7 +3,7 @@ ruamel.yaml==0.18.6 msgpack==1.0.8 tqdm==4.66.2 pymongo==4.6.3 -pandas==2.2.1 +pandas==2.2.2 orjson==3.10.0 types-orjson==3.6.2 types-requests==2.31.0.20240403 From 4e87a038bbe69d8d36d0e4fd779522dc1c9acd32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:13:23 +0000 Subject: [PATCH 31/31] Bump types-requests from 2.31.0.20240403 to 2.31.0.20240406 Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240403 to 2.31.0.20240406. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index 997c0b896..546f4ff29 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -6,4 +6,4 @@ pymongo==4.6.3 pandas==2.2.2 orjson==3.10.0 types-orjson==3.6.2 -types-requests==2.31.0.20240403 +types-requests==2.31.0.20240406