From 36131f77d2d3c00566882945e3f53c8b97eec6cd Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 10:40:13 +0800 Subject: [PATCH 01/22] add type hints --- monty/dev.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index efa7a0c5..6cde0bf5 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -7,11 +7,16 @@ import logging import sys import warnings +from typing import Callable, Optional, Type logger = logging.getLogger(__name__) -def deprecated(replacement=None, message=None, category=FutureWarning): +def deprecated( + replacement: Optional[Callable] = None, + message: Optional[str] = None, + category: Type[Warning] = FutureWarning, +): """ Decorator to mark classes or functions as deprecated, with a possible replacement. @@ -19,7 +24,7 @@ def deprecated(replacement=None, message=None, category=FutureWarning): replacement (callable): A replacement class or method. message (str): A warning message to be displayed. category (Warning): Choose the category of the warning to issue. Defaults - to FutureWarning. Another choice can be DeprecationWarning. NOte that + to FutureWarning. Another choice can be DeprecationWarning. Note that FutureWarning is meant for end users and is always shown unless silenced. DeprecationWarning is meant for developers and is never shown unless python is run in developmental mode or the filter is changed. Make @@ -29,7 +34,7 @@ def deprecated(replacement=None, message=None, category=FutureWarning): Original function, but with a warning to use the updated class. """ - def craft_message(old, replacement, message): + def craft_message(old: Callable, replacement: Callable, message: str): msg = f"{old.__name__} is deprecated" if replacement is not None: if isinstance(replacement, property): @@ -43,7 +48,7 @@ def craft_message(old, replacement, message): msg += "\n" + message return msg - def deprecated_decorator(old): + def deprecated_decorator(old: Callable): def wrapped(*args, **kwargs): msg = craft_message(old, replacement, message) warnings.warn(msg, category=category, stacklevel=2) @@ -101,7 +106,7 @@ def decorated(*args, **kwargs): return decorated -def install_excepthook(hook_type="color", **kwargs): +def install_excepthook(hook_type: str = "color", **kwargs): """ This function replaces the original python traceback with an improved version from Ipython. Use `color` for colourful traceback formatting, From 05d77e0d08a84b8e732a53141fee0be749239b66 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 11:09:31 +0800 Subject: [PATCH 02/22] add deprecation deadline --- monty/dev.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 6cde0bf5..13bd3e6b 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -5,8 +5,10 @@ import functools import logging +import os import sys import warnings +from datetime import datetime from typing import Callable, Optional, Type logger = logging.getLogger(__name__) @@ -14,7 +16,8 @@ def deprecated( replacement: Optional[Callable] = None, - message: Optional[str] = None, + message: str = "", + deadline: Optional[datetime] = None, category: Type[Warning] = FutureWarning, ): """ @@ -23,6 +26,8 @@ def deprecated( Args: replacement (callable): A replacement class or method. message (str): A warning message to be displayed. + deadline (datetime): Optional deadline for removal of the old function/class. + A CI error would be raised after this date. category (Warning): Choose the category of the warning to issue. Defaults to FutureWarning. Another choice can be DeprecationWarning. Note that FutureWarning is meant for end users and is always shown unless silenced. @@ -34,8 +39,17 @@ def deprecated( Original function, but with a warning to use the updated class. """ - def craft_message(old: Callable, replacement: Callable, message: str): + def craft_message( + old: Callable, + replacement: Callable, + message: str, + deadline: datetime, + ): msg = f"{old.__name__} is deprecated" + + if deadline is not None: + msg += f", and would be removed on {deadline.strftime('%Y-%m-%d')}\n" + if replacement is not None: if isinstance(replacement, property): r = replacement.fget @@ -44,18 +58,23 @@ def craft_message(old: Callable, replacement: Callable, message: str): else: r = replacement msg += f"; use {r.__name__} in {r.__module__} instead." - if message is not None: + + if message: msg += "\n" + message return msg def deprecated_decorator(old: Callable): def wrapped(*args, **kwargs): - msg = craft_message(old, replacement, message) + msg = craft_message(old, replacement, message, deadline) warnings.warn(msg, category=category, stacklevel=2) return old(*args, **kwargs) return wrapped + # Raise a CI error after removal deadline + if deadline is not None and "CI" in os.environ and datetime.now() > deadline: + raise RuntimeError("This function should have been removed.") + return deprecated_decorator From a51fb8d761dcfdef33e4b0a210d83229030f5c0c Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 11:19:52 +0800 Subject: [PATCH 03/22] remove unused deps --- requirements-ci.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/requirements-ci.txt b/requirements-ci.txt index a442bfcf..70838536 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,12 +1,9 @@ pytest pytest-cov coverage -coveralls pycodestyle mypy -pydocstyle pydantic -flake8 black pylint torch From 2956082a20f4d09e32c35a43eaf04b92e9feb1f3 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 11:23:00 +0800 Subject: [PATCH 04/22] replace deprecated func --- monty/json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monty/json.py b/monty/json.py index ea1f6e3e..6c2fdac2 100644 --- a/monty/json.py +++ b/monty/json.py @@ -328,7 +328,7 @@ def __get_pydantic_core_schema__(cls, source_type, handler): if core_schema is None: raise RuntimeError("Pydantic >= 2.0 is required for validation") - s = core_schema.general_plain_validator_function(cls.validate_monty_v2) + s = core_schema.with_info_plain_validator_function(cls.validate_monty_v2) return core_schema.json_or_python_schema(json_schema=s, python_schema=s) From a39ca06792243d8e79a4257ae7c31ea69338453e Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 11:23:25 +0800 Subject: [PATCH 05/22] remove unused class def --- tests/test_dev.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 855f2f8a..5a024085 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -6,17 +6,6 @@ from monty.dev import deprecated, install_excepthook, requires -class A: - @property - def repl_prop(self): - pass - - @deprecated(repl_prop) # type: ignore - @property - def prop(self): - pass - - class TestDecorator: def test_deprecated(self): def func_a(): From 4bdc371a6eefcfd3453f1d2ecd1126b92ed91164 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 12:14:15 +0800 Subject: [PATCH 06/22] tweak test namings --- tests/test_dev.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 5a024085..02060f6f 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -2,32 +2,30 @@ import warnings import pytest - from monty.dev import deprecated, install_excepthook, requires class TestDecorator: def test_deprecated(self): - def func_a(): + def func_replace(): pass - @deprecated(func_a, "hello") - def func_b(): + @deprecated(func_replace, "Use func_replace instead") + def func_old(): pass with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") # Trigger a warning. - func_b() - # Verify some things + func_old() + # Verify Warning and message assert issubclass(w[0].category, FutureWarning) - assert "hello" in str(w[0].message) + assert "Use func_replace instead" in str(w[0].message) def test_deprecated_property(self): - class a: - def __init__(self): - pass + class TestClass: + """A dummy class for tests.""" @property def property_a(self): @@ -46,22 +44,21 @@ def func_a(self): # Cause all warnings to always be triggered. warnings.simplefilter("always") # Trigger a warning. - assert a().property_b == "b" - # Verify some things + assert TestClass().property_b == "b" + # Verify warning type assert issubclass(w[-1].category, FutureWarning) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") # Trigger a warning. - assert a().func_a() == "a" + assert TestClass().func_a() == "a" # Verify some things assert issubclass(w[-1].category, FutureWarning) def test_deprecated_classmethod(self): - class A: - def __init__(self): - pass + class TestClass: + """A dummy class for tests.""" @classmethod def classmethod_a(self): @@ -76,13 +73,12 @@ def classmethod_b(self): # Cause all warnings to always be triggered. warnings.simplefilter("always") # Trigger a warning. - assert A().classmethod_b() == "b" + assert TestClass().classmethod_b() == "b" # Verify some things assert issubclass(w[-1].category, FutureWarning) - class A: - def __init__(self): - pass + class TestClass: + """A dummy class for tests.""" @classmethod def classmethod_a(self): @@ -94,7 +90,7 @@ def classmethod_b(self): return "b" with pytest.warns(DeprecationWarning): - assert A().classmethod_b() == "b" + assert TestClass().classmethod_b() == "b" def test_requires(self): try: From 2088f8d3b32aec7aa918983badcc4805e07ff7fc Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 12:15:36 +0800 Subject: [PATCH 07/22] make always warn global --- tests/test_dev.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 02060f6f..ad8034e5 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -4,6 +4,9 @@ import pytest from monty.dev import deprecated, install_excepthook, requires +# Set all warnings to always be triggered. +warnings.simplefilter("always") + class TestDecorator: def test_deprecated(self): @@ -15,8 +18,6 @@ def func_old(): pass with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") # Trigger a warning. func_old() # Verify Warning and message @@ -41,16 +42,12 @@ def func_a(self): return "a" with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") # Trigger a warning. assert TestClass().property_b == "b" # Verify warning type assert issubclass(w[-1].category, FutureWarning) with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") # Trigger a warning. assert TestClass().func_a() == "a" # Verify some things @@ -70,8 +67,6 @@ def classmethod_b(self): return "b" with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") # Trigger a warning. assert TestClass().classmethod_b() == "b" # Verify some things From cb914176a68f3bf35cccc05ab9fbf167ee71a12c Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 12:39:33 +0800 Subject: [PATCH 08/22] add test for deprecation deadline --- tests/test_dev.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_dev.py b/tests/test_dev.py index ad8034e5..e36bbe99 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,5 +1,6 @@ import unittest import warnings +from datetime import datetime import pytest from monty.dev import deprecated, install_excepthook, requires @@ -87,6 +88,17 @@ def classmethod_b(self): with pytest.warns(DeprecationWarning): assert TestClass().classmethod_b() == "b" + def test_deprecated_deadline(self): + @deprecated(deadline=datetime(2000, 1, 1)) + def func_old(): + pass + + with warnings.catch_warnings(record=True) as w: + # Trigger a warning. + func_old() + # Verify message + assert "would be removed on 2000-01-01" in str(w[0].message) + def test_requires(self): try: import fictitious_mod # type: ignore From e97cfe99813f0a2c4dc3e5a8a2a05349de99b639 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 16:27:26 +0800 Subject: [PATCH 09/22] remove non-existence get_ncpus method --- docs/monty.dev.md | 13 ------------- docs/monty.md | 1 - 2 files changed, 14 deletions(-) diff --git a/docs/monty.dev.md b/docs/monty.dev.md index ebd3099c..a6ee075b 100644 --- a/docs/monty.dev.md +++ b/docs/monty.dev.md @@ -26,19 +26,6 @@ with a possible replacement. * **Returns** Original function, but with a warning to use the updated class. -## monty.dev.get_ncpus() - -**NOTE**: If you are using Python >= 2.7, multiprocessing.cpu_count() already -provides the number of CPUs. In fact, this is the first method tried. -The purpose of this function is to cater to old Python versions that -still exist on many Linux style clusters. - -Number of virtual or physical CPUs on this system, i.e. -user/real as output by time(1) when called with an optimally scaling -userspace-only program. Return -1 if ncpus cannot be detected. Taken from: -[http://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of](http://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of)- -cpus-in-python - ## monty.dev.install_excepthook(hook_type=’color’, \*\*kwargs) This function replaces the original python traceback with an improved diff --git a/docs/monty.md b/docs/monty.md index 60e14938..013388d8 100644 --- a/docs/monty.md +++ b/docs/monty.md @@ -44,7 +44,6 @@ useful design patterns such as singleton and cached_class, and many more. * `singleton()` * [monty.dev module](monty.dev.md) * `deprecated()` - * `get_ncpus()` * `install_excepthook()` * `requires` * [monty.fnmatch module](monty.fnmatch.md) From 783a982464769dc44a06b544272da9b9d6f36aca Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 16:31:45 +0800 Subject: [PATCH 10/22] adjust warning type and message --- monty/dev.py | 6 ++++-- tests/test_dev.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 13bd3e6b..80df5839 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -48,7 +48,7 @@ def craft_message( msg = f"{old.__name__} is deprecated" if deadline is not None: - msg += f", and would be removed on {deadline.strftime('%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): @@ -73,7 +73,9 @@ def wrapped(*args, **kwargs): # Raise a CI error after removal deadline if deadline is not None and "CI" in os.environ and datetime.now() > deadline: - raise RuntimeError("This function should have been removed.") + raise DeprecationWarning( + "This function should have been removed on {deadline:%Y-%m-%d}." + ) return deprecated_decorator diff --git a/tests/test_dev.py b/tests/test_dev.py index e36bbe99..f268e4c1 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -97,7 +97,7 @@ def func_old(): # Trigger a warning. func_old() # Verify message - assert "would be removed on 2000-01-01" in str(w[0].message) + assert "will be removed on 2000-01-01" in str(w[0].message) def test_requires(self): try: From d5b463bc6aac57b49df92f698b19e177ddf52a35 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 16:38:07 +0800 Subject: [PATCH 11/22] use stricter "os.getenv("CI")" --- monty/dev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monty/dev.py b/monty/dev.py index 80df5839..459af8c0 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -72,7 +72,7 @@ def wrapped(*args, **kwargs): return wrapped # Raise a CI error after removal deadline - if deadline is not None and "CI" in os.environ and datetime.now() > deadline: + if deadline is not None and os.getenv("CI") and datetime.now() > deadline: raise DeprecationWarning( "This function should have been removed on {deadline:%Y-%m-%d}." ) From 5cca0dc0faa6359933c91b6a28c292dabc2408a1 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 16:51:38 +0800 Subject: [PATCH 12/22] use int tuple as deadline --- monty/dev.py | 28 ++++++++++++++++++++-------- tests/test_dev.py | 3 +-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 459af8c0..186ca9c1 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -17,7 +17,7 @@ def deprecated( replacement: Optional[Callable] = None, message: str = "", - deadline: Optional[datetime] = None, + deadline: Optional[tuple[int, int, int]] = None, category: Type[Warning] = FutureWarning, ): """ @@ -26,8 +26,9 @@ def deprecated( Args: replacement (callable): A replacement class or method. message (str): A warning message to be displayed. - deadline (datetime): Optional deadline for removal of the old function/class. - A CI error would be raised after this date. + deadline (Optional[tuple[int, int, int]]): Optional deadline for removal + of the old function/class, in format (yyyy, MM, dd). A CI error would + be raised after this date. category (Warning): Choose the category of the warning to issue. Defaults to FutureWarning. Another choice can be DeprecationWarning. Note that FutureWarning is meant for end users and is always shown unless silenced. @@ -39,6 +40,12 @@ def deprecated( Original function, but with a warning to use the updated class. """ + def _convert_date(date: tuple[int, int, int]) -> datetime: + """Convert a date in int tuple for datetime type. + Expect the date in (yyyy, MM, dd) format. + """ + return datetime(*date) + def craft_message( old: Callable, replacement: Callable, @@ -65,17 +72,22 @@ def craft_message( def deprecated_decorator(old: Callable): def wrapped(*args, **kwargs): - msg = craft_message(old, replacement, message, deadline) + msg = craft_message(old, replacement, message, _deadline) warnings.warn(msg, category=category, stacklevel=2) return old(*args, **kwargs) return wrapped # Raise a CI error after removal deadline - if deadline is not None and os.getenv("CI") and datetime.now() > deadline: - raise DeprecationWarning( - "This function should have been removed on {deadline:%Y-%m-%d}." - ) + if deadline is None: + _deadline = None + + else: + _deadline = _convert_date(deadline) + if os.getenv("CI") and datetime.now() > _deadline: + raise DeprecationWarning( + "This function should have been removed on {deadline:%Y-%m-%d}." + ) return deprecated_decorator diff --git a/tests/test_dev.py b/tests/test_dev.py index f268e4c1..06a8e31e 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,6 +1,5 @@ import unittest import warnings -from datetime import datetime import pytest from monty.dev import deprecated, install_excepthook, requires @@ -89,7 +88,7 @@ def classmethod_b(self): assert TestClass().classmethod_b() == "b" def test_deprecated_deadline(self): - @deprecated(deadline=datetime(2000, 1, 1)) + @deprecated(deadline=(2000, 1, 1)) def func_old(): pass From 2901b9d8b48003dfba27bea1a6b0a57e5ce4ee58 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Tue, 27 Feb 2024 16:58:13 +0800 Subject: [PATCH 13/22] tweak docstring --- docs/monty.dev.md | 2 +- monty/dev.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/monty.dev.md b/docs/monty.dev.md index a6ee075b..ec3f4db4 100644 --- a/docs/monty.dev.md +++ b/docs/monty.dev.md @@ -18,7 +18,7 @@ with a possible replacement. * **replacement** (*callable*) – A replacement class or method. * **message** (*str*) – A warning message to be displayed. * **category** (*Warning*) – Choose the category of the warning to issue. Defaults - to FutureWarning. Another choice can be DeprecationWarning. NOte that + to FutureWarning. Another choice can be DeprecationWarning. Note that FutureWarning is meant for end users and is always shown unless silenced. DeprecationWarning is meant for developers and is never shown unless python is run in developmental mode or the filter is changed. Make diff --git a/monty/dev.py b/monty/dev.py index 186ca9c1..00e00fea 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -41,9 +41,7 @@ def deprecated( """ def _convert_date(date: tuple[int, int, int]) -> datetime: - """Convert a date in int tuple for datetime type. - Expect the date in (yyyy, MM, dd) format. - """ + """Convert date as int tuple (yyyy, MM, dd) to datetime type.""" return datetime(*date) def craft_message( From bb4af84d6b10243b1c3e3c8d9b81f98ff83d3df6 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Thu, 7 Mar 2024 10:11:57 +0800 Subject: [PATCH 14/22] fix naming with "warning" --- monty/dev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 00e00fea..4117b076 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -27,7 +27,7 @@ def deprecated( replacement (callable): A replacement class or method. message (str): A warning message to be displayed. deadline (Optional[tuple[int, int, int]]): Optional deadline for removal - of the old function/class, in format (yyyy, MM, dd). A CI error would + of the old function/class, in format (yyyy, MM, dd). A CI warning would be raised after this date. category (Warning): Choose the category of the warning to issue. Defaults to FutureWarning. Another choice can be DeprecationWarning. Note that @@ -76,7 +76,7 @@ def wrapped(*args, **kwargs): return wrapped - # Raise a CI error after removal deadline + # Raise a CI warning after removal deadline if deadline is None: _deadline = None From 59458a2faf7fc56083bf8ea34ef59e831db004ac Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Fri, 8 Mar 2024 10:44:11 +0800 Subject: [PATCH 15/22] add test for code owner --- monty/dev.py | 55 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 4117b076..8c920826 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -7,6 +7,7 @@ import logging import os import sys +import subprocess import warnings from datetime import datetime from typing import Callable, Optional, Type @@ -28,7 +29,7 @@ def deprecated( message (str): A warning message to be displayed. deadline (Optional[tuple[int, int, int]]): Optional deadline for removal of the old function/class, in format (yyyy, MM, dd). A CI warning would - be raised after this date. + be raised after this date if is running in code owner' repo. category (Warning): Choose the category of the warning to issue. Defaults to FutureWarning. Another choice can be DeprecationWarning. Note that FutureWarning is meant for end users and is always shown unless silenced. @@ -42,8 +43,47 @@ def deprecated( def _convert_date(date: tuple[int, int, int]) -> datetime: """Convert date as int tuple (yyyy, MM, dd) to datetime type.""" + return datetime(*date) + def deadline_warning() -> None: + """Raise CI warning after removal deadline in code owner's repo.""" + + def _is_in_owner_repo() -> bool: + """Check if is running in code owner's repo. + Only generate reliable check when `git` is installed and remote name + is "origin". + """ + + try: + # Get current running repo + result = subprocess.run( + ["git", "config", "--get", "remote.origin.url"], + stdout=subprocess.PIPE, + ) + owner_repo = ( + result.stdout.decode("utf-8") + .strip() + .lstrip("git@github.com:") + .rstrip(".git") + ) + + return owner_repo == os.getenv("GITHUB_REPOSITORY") + + except (subprocess.CalledProcessError, FileNotFoundError): + return False + + # Only raise warning in code owner's repo CI + if ( + _deadline is not None + and os.getenv("CI") + and datetime.now() > _deadline + and _is_in_owner_repo() + ): + raise DeprecationWarning( + "This function should have been removed on {deadline:%Y-%m-%d}." + ) + def craft_message( old: Callable, replacement: Callable, @@ -76,16 +116,11 @@ def wrapped(*args, **kwargs): return wrapped - # Raise a CI warning after removal deadline - if deadline is None: - _deadline = None + # Convert deadline to datetime + _deadline = _convert_date(deadline) if deadline is not None else None - else: - _deadline = _convert_date(deadline) - if os.getenv("CI") and datetime.now() > _deadline: - raise DeprecationWarning( - "This function should have been removed on {deadline:%Y-%m-%d}." - ) + # Raise a CI warning after removal deadline + deadline_warning() return deprecated_decorator From a37e12cb170d980e2b2b3741d626ee146c430464 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Fri, 8 Mar 2024 17:57:41 +0800 Subject: [PATCH 16/22] add lstrip for https clone --- monty/dev.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 8c920826..99a8467a 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -64,8 +64,9 @@ def _is_in_owner_repo() -> bool: owner_repo = ( result.stdout.decode("utf-8") .strip() - .lstrip("git@github.com:") - .rstrip(".git") + .lstrip("https://github.com/") # https clone + .lstrip("git@github.com:") # ssh clone + .rstrip(".git") # ssh clone ) return owner_repo == os.getenv("GITHUB_REPOSITORY") From 0a137308f10e96444086a9403c85515e7d95b2e5 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 11:36:13 +0800 Subject: [PATCH 17/22] revise warn method name --- monty/dev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 99a8467a..44d27094 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -46,7 +46,7 @@ def _convert_date(date: tuple[int, int, int]) -> datetime: return datetime(*date) - def deadline_warning() -> None: + def raise_deadline_warning() -> None: """Raise CI warning after removal deadline in code owner's repo.""" def _is_in_owner_repo() -> bool: @@ -121,7 +121,7 @@ def wrapped(*args, **kwargs): _deadline = _convert_date(deadline) if deadline is not None else None # Raise a CI warning after removal deadline - deadline_warning() + raise_deadline_warning() return deprecated_decorator From a2477642ca3a9fdef0395fbc40600efc34a54def Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 11:44:22 +0800 Subject: [PATCH 18/22] add no warn tests --- tests/test_dev.py | 51 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 06a8e31e..4fe8595f 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -1,5 +1,6 @@ import unittest import warnings +import datetime import pytest from monty.dev import deprecated, install_excepthook, requires @@ -58,12 +59,12 @@ class TestClass: """A dummy class for tests.""" @classmethod - def classmethod_a(self): + def classmethod_a(cls): pass @classmethod @deprecated(classmethod_a) - def classmethod_b(self): + def classmethod_b(cls): return "b" with warnings.catch_warnings(record=True) as w: @@ -72,20 +73,20 @@ def classmethod_b(self): # Verify some things assert issubclass(w[-1].category, FutureWarning) - class TestClass: + class TestClass_deprecationwarning: """A dummy class for tests.""" @classmethod - def classmethod_a(self): + def classmethod_a(cls): pass @classmethod @deprecated(classmethod_a, category=DeprecationWarning) - def classmethod_b(self): + def classmethod_b(cls): return "b" with pytest.warns(DeprecationWarning): - assert TestClass().classmethod_b() == "b" + assert TestClass_deprecationwarning().classmethod_b() == "b" def test_deprecated_deadline(self): @deprecated(deadline=(2000, 1, 1)) @@ -98,6 +99,44 @@ def func_old(): # Verify message assert "will be removed on 2000-01-01" in str(w[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 + with warnings.catch_warnings(record=True) as w: + monkeypatch.setattr( + datetime, "datetime", lambda: datetime.datetime(1999, 1, 1) + ) + func_old() + + for warning in w: + 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 w: + monkeypatch.delenv("CI", raising=False) + func_old() + + for warning in w: + 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 w: + monkeypatch.setenv("GITHUB_REPOSITORY", "NONE/NONE") + func_old() + + for warning in w: + assert "This function should have been removed on" not in str( + warning.message + ) + def test_requires(self): try: import fictitious_mod # type: ignore From 247b297b705158e3b5665119ed49265413e34a62 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 13:57:08 +0800 Subject: [PATCH 19/22] replace single letter var --- tests/test_dev.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 4fe8595f..236934ea 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -93,11 +93,11 @@ def test_deprecated_deadline(self): def func_old(): pass - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(record=True) as warn_msgs: # Trigger a warning. func_old() # Verify message - assert "will be removed on 2000-01-01" in str(w[0].message) + assert "will be removed on 2000-01-01" in str(warn_msgs[0].message) def test_deprecated_deadline_no_warn(self, monkeypatch): # Test cases where no warning should be raised @@ -106,33 +106,33 @@ def func_old(): pass # No warn case 1: date before deadline - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.setattr( datetime, "datetime", lambda: datetime.datetime(1999, 1, 1) ) func_old() - for warning in w: + 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 w: + with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.delenv("CI", raising=False) func_old() - for warning in w: + 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 w: + with warnings.catch_warnings(record=True) as warn_msgs: monkeypatch.setenv("GITHUB_REPOSITORY", "NONE/NONE") func_old() - for warning in w: + for warning in warn_msgs: assert "This function should have been removed on" not in str( warning.message ) From 5d62012cd00215eac9ed4c40f606f802b66bac72 Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 13:59:22 +0800 Subject: [PATCH 20/22] remove unnecessary date convert func --- monty/dev.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/monty/dev.py b/monty/dev.py index 44d27094..35993307 100644 --- a/monty/dev.py +++ b/monty/dev.py @@ -41,11 +41,6 @@ def deprecated( Original function, but with a warning to use the updated class. """ - def _convert_date(date: tuple[int, int, int]) -> datetime: - """Convert date as int tuple (yyyy, MM, dd) to datetime type.""" - - return datetime(*date) - def raise_deadline_warning() -> None: """Raise CI warning after removal deadline in code owner's repo.""" @@ -117,8 +112,8 @@ def wrapped(*args, **kwargs): return wrapped - # Convert deadline to datetime - _deadline = _convert_date(deadline) if deadline is not None else None + # 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() From 80bfea5a9839e2e548cc24f4c2975f077da1b49a Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 14:32:31 +0800 Subject: [PATCH 21/22] replace | operator with Union --- tests/test_json.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_json.py b/tests/test_json.py index a6571a17..7ad2731a 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -6,6 +6,7 @@ import os import pathlib from enum import Enum +from typing import Union try: import numpy as np @@ -825,7 +826,7 @@ class ModelWithLimited(BaseModel): a: LimitedMSONClass class ModelWithUnion(BaseModel): - a: LimitedMSONClass | dict + a: Union[LimitedMSONClass, dict] limited_dict = jsanitize(ModelWithLimited(a=LimitedMSONClass(1)), strict=True) assert ModelWithLimited.model_validate(limited_dict) From de31119174fdc815a487bfb02fc55f01fd4c5dbc Mon Sep 17 00:00:00 2001 From: Haoyu Yang Date: Mon, 11 Mar 2024 16:04:58 +0800 Subject: [PATCH 22/22] convert comment to docstring --- tests/test_dev.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_dev.py b/tests/test_dev.py index 236934ea..6b9816a7 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -100,7 +100,8 @@ def func_old(): assert "will be removed on 2000-01-01" in str(warn_msgs[0].message) def test_deprecated_deadline_no_warn(self, monkeypatch): - # Test cases where no warning should be raised + """Test cases where no warning should be raised.""" + @deprecated(deadline=(2000, 1, 1)) def func_old(): pass