diff --git a/pyiron_base/__init__.py b/pyiron_base/__init__.py index 0faab3f7b..e0149a6bd 100644 --- a/pyiron_base/__init__.py +++ b/pyiron_base/__init__.py @@ -12,7 +12,6 @@ from pyiron_base.storage.has_stored_traits import HasStoredTraits from pyiron_base.storage.inputlist import InputList from pyiron_base.storage.parameters import GenericParameters -from pyiron_base.utils.deprecate import Deprecator, deprecate, deprecate_soon from pyiron_base.jobs.job.extension.executable import Executable from pyiron_base.project.external import Notebook, load, dump from pyiron_base.jobs.dynamic import warn_dynamic_job_classes @@ -45,9 +44,6 @@ from pyiron_base.jobs.job.toolkit import Toolkit, BaseTools -# Expose snippets references in base API for backwards compatibility -from pyiron_snippets.import_alarm import ImportAlarm - # Internal init from ._version import get_versions from pyiron_base.utils.jedi import fix_ipython_autocomplete @@ -76,10 +72,6 @@ HasStoredTraits, InputList, GenericParameters, - Deprecator, - deprecate, - deprecate_soon, - ImportAlarm, Executable, Notebook, load, diff --git a/pyiron_base/database/generic.py b/pyiron_base/database/generic.py index ea33a6da1..bce570902 100644 --- a/pyiron_base/database/generic.py +++ b/pyiron_base/database/generic.py @@ -5,13 +5,13 @@ DatabaseAccess class deals with accessing the database """ -from pyiron_base.state.logger import logger import numpy as np import re import os from datetime import datetime -from pyiron_base.utils.deprecate import deprecate import pandas +from pyiron_snippets.deprecate import deprecate +from pyiron_snippets.logger import logger from sqlalchemy import ( create_engine, MetaData, diff --git a/pyiron_base/database/interface.py b/pyiron_base/database/interface.py index a7d2df0c6..d100f8d0e 100644 --- a/pyiron_base/database/interface.py +++ b/pyiron_base/database/interface.py @@ -5,14 +5,14 @@ DatabaseAccess class deals with accessing the database """ -from pyiron_base.state.logger import logger from abc import ABC, abstractmethod from collections.abc import Iterable import warnings import numpy as np import re -from pyiron_base.utils.deprecate import deprecate import pandas +from pyiron_snippets.deprecate import deprecate +from pyiron_snippets.logger import logger import typing import fnmatch diff --git a/pyiron_base/database/manager.py b/pyiron_base/database/manager.py index 6974075ec..76805376a 100644 --- a/pyiron_base/database/manager.py +++ b/pyiron_base/database/manager.py @@ -6,7 +6,7 @@ """ from urllib.parse import quote_plus -from pyiron_base.state.logger import logger +from pyiron_snippets.logger import logger from pyiron_snippets.singleton import Singleton from pyiron_base.state.settings import settings as s import os diff --git a/pyiron_base/jobs/datamining.py b/pyiron_base/jobs/datamining.py index 5206e984e..245e531a5 100644 --- a/pyiron_base/jobs/datamining.py +++ b/pyiron_base/jobs/datamining.py @@ -11,11 +11,11 @@ import os import pandas from pandas.errors import EmptyDataError +from pyiron_snippets.deprecate import deprecate from tqdm.auto import tqdm import types from typing import List, Tuple -from pyiron_base.utils.deprecate import deprecate from pyiron_base.jobs.job.generic import GenericJob from pyiron_base.jobs.job.extension import jobstatus from pyiron_base.storage.hdfio import FileHDFio diff --git a/pyiron_base/jobs/job/core.py b/pyiron_base/jobs/job/core.py index af2202f1b..62b33add0 100644 --- a/pyiron_base/jobs/job/core.py +++ b/pyiron_base/jobs/job/core.py @@ -12,6 +12,8 @@ import shutil import warnings +from pyiron_snippets.deprecate import deprecate + from pyiron_base.interfaces.has_groups import HasGroups from pyiron_base.storage.hdfio import ProjectHDFio from pyiron_base.jobs.job.util import ( @@ -33,7 +35,6 @@ _job_remove_folder, ) from pyiron_base.state import state -from pyiron_base.utils.deprecate import deprecate from pyiron_base.jobs.job.extension.files import FileBrowser __author__ = "Jan Janssen" diff --git a/pyiron_base/jobs/job/extension/server/generic.py b/pyiron_base/jobs/job/extension/server/generic.py index 3f764ff53..7fea939cf 100644 --- a/pyiron_base/jobs/job/extension/server/generic.py +++ b/pyiron_base/jobs/job/extension/server/generic.py @@ -10,10 +10,11 @@ import socket from typing import Union +from pyiron_snippets.deprecate import deprecate + from pyiron_base.state import state from pyiron_base.interfaces.lockable import Lockable, sentinel from pyiron_base.interfaces.has_dict import HasDict -from pyiron_base.utils.deprecate import deprecate from pyiron_base.utils.instance import static_isinstance from pyiron_base.jobs.job.extension.server.runmode import Runmode diff --git a/pyiron_base/jobs/job/generic.py b/pyiron_base/jobs/job/generic.py index 3a558ed40..fd46386d2 100644 --- a/pyiron_base/jobs/job/generic.py +++ b/pyiron_base/jobs/job/generic.py @@ -14,6 +14,7 @@ import warnings from h5io_browser.base import _read_hdf, _write_hdf +from pyiron_snippets.deprecate import deprecate from pyiron_base.state import state from pyiron_base.state.signal import catch_signals @@ -47,7 +48,6 @@ _job_reload_after_copy, ) from pyiron_base.utils.instance import static_isinstance, import_class -from pyiron_base.utils.deprecate import deprecate from pyiron_base.jobs.job.extension.server.generic import Server from pyiron_base.database.filetable import FileTable from pyiron_base.interfaces.has_dict import HasDict diff --git a/pyiron_base/jobs/job/runfunction.py b/pyiron_base/jobs/job/runfunction.py index 547ba3a67..a774fd589 100644 --- a/pyiron_base/jobs/job/runfunction.py +++ b/pyiron_base/jobs/job/runfunction.py @@ -9,8 +9,8 @@ import subprocess from jinja2 import Template +from pyiron_snippets.deprecate import deprecate -from pyiron_base.utils.deprecate import deprecate from pyiron_base.jobs.job.wrapper import JobWrapper from pyiron_base.state import state from pyiron_base.state.signal import catch_signals diff --git a/pyiron_base/jobs/master/parallel.py b/pyiron_base/jobs/master/parallel.py index 9c2bb571b..d83ad5839 100644 --- a/pyiron_base/jobs/master/parallel.py +++ b/pyiron_base/jobs/master/parallel.py @@ -11,6 +11,7 @@ import numpy as np import pandas +from pyiron_snippets.deprecate import deprecate import multiprocessing import importlib from pyiron_base.jobs.job.generic import GenericJob @@ -21,7 +22,6 @@ from pyiron_base.state import state from pyiron_base.jobs.job.wrapper import job_wrapper_function from pyiron_base.jobs.job.util import _get_safe_job_name -from pyiron_base.utils.deprecate import deprecate __author__ = "Joerg Neugebauer, Jan Janssen" __copyright__ = ( diff --git a/pyiron_base/project/generic.py b/pyiron_base/project/generic.py index a8a395edf..3b95847bb 100644 --- a/pyiron_base/project/generic.py +++ b/pyiron_base/project/generic.py @@ -13,6 +13,7 @@ import stat from tqdm.auto import tqdm import pandas +from pyiron_snippets.deprecate import deprecate import numpy as np from pyiron_base.project.jobloader import JobLoader, JobInspector @@ -27,7 +28,6 @@ get_job_status, ) from pyiron_base.storage.hdfio import ProjectHDFio -from pyiron_base.utils.deprecate import deprecate from pyiron_base.interfaces.has_groups import HasGroups from pyiron_base.jobs.flex.factory import create_job_factory from pyiron_base.jobs.job.util import _special_symbol_replacements, _get_safe_job_name diff --git a/pyiron_base/state/__init__.py b/pyiron_base/state/__init__.py index db573c65f..fa8308d77 100644 --- a/pyiron_base/state/__init__.py +++ b/pyiron_base/state/__init__.py @@ -14,11 +14,11 @@ compatibility. """ -from pyiron_base.state.logger import logger as _logger from pyiron_base.database.manager import database as _database from pyiron_base.state.publications import publications as _publications from pyiron_base.state.queue_adapter import queue_adapters as _queue_adapters from pyiron_base.state.settings import settings as _settings +from pyiron_snippets.logger import logger as _logger from pyiron_snippets.singleton import Singleton from typing import Dict, Union diff --git a/pyiron_base/state/logger.py b/pyiron_base/state/logger.py deleted file mode 100644 index b80d48055..000000000 --- a/pyiron_base/state/logger.py +++ /dev/null @@ -1,73 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -import logging -from types import MethodType - -__author__ = "Joerg Neugebauer" -__copyright__ = ( - "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - " - "Computational Materials Design (CM) Department" -) -__version__ = "1.0" -__maintainer__ = "Jan Janssen" -__email__ = "janssen@mpie.de" -__status__ = "production" -__date__ = "Sep 1, 2017" - - -""" -Set the logging level for pyiron -""" - - -def set_logging_level(self, level, channel=None): - """ - Set level for logger - - Args: - level (str): 'DEBUG, INFO, WARN' - channel (int): 0: file_log, 1: stream, None: both - """ - - if channel: - self.handlers[channel].setLevel(level) - else: - self.handlers[0].setLevel(level) - self.handlers[1].setLevel(level) - - -def setup_logger(): - """ - Setup logger - logs are written to pyiron.log - - Returns: - logging.getLogger: Logger - """ - logger = logging.getLogger("pyiron_log") - logger.setLevel(logging.DEBUG) - - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - - ch = logging.StreamHandler() - ch.setLevel(logging.WARN) - ch.setFormatter(formatter) - logger.addHandler(ch) - - try: - fh = logging.FileHandler("pyiron.log") - except PermissionError: - pass - else: - fh.setLevel(logging.INFO) - fh.setFormatter(formatter) - logger.addHandler(fh) - - return logger - - -logger = setup_logger() -logger.set_logging_level = MethodType(set_logging_level, logger) diff --git a/pyiron_base/state/settings.py b/pyiron_base/state/settings.py index 2e093fca1..3412e602c 100644 --- a/pyiron_base/state/settings.py +++ b/pyiron_base/state/settings.py @@ -36,10 +36,10 @@ import os import warnings from configparser import ConfigParser -from pyiron_base.state.logger import logger from pyiron_base.state.publications import publications from pyiron_base.utils.strtobool import strtobool from pathlib import Path +from pyiron_snippets.logger import logger from pyiron_snippets.singleton import Singleton from typing import Union, Dict, List from copy import deepcopy diff --git a/pyiron_base/storage/hdfio.py b/pyiron_base/storage/hdfio.py index 0a4a696e1..c0d62210d 100644 --- a/pyiron_base/storage/hdfio.py +++ b/pyiron_base/storage/hdfio.py @@ -17,11 +17,11 @@ import importlib import pandas import posixpath +from pyiron_snippets.deprecate import deprecate import numpy as np import sys from typing import Union, Optional, Any, Tuple -from pyiron_base.utils.deprecate import deprecate from pyiron_base.storage.helper_functions import ( get_h5_path, list_groups_and_nodes, diff --git a/pyiron_base/storage/inputlist.py b/pyiron_base/storage/inputlist.py index 81c52c63f..68970a3b8 100644 --- a/pyiron_base/storage/inputlist.py +++ b/pyiron_base/storage/inputlist.py @@ -2,8 +2,9 @@ Backwards compatible way of importing the DataContainer. """ +from pyiron_snippets.deprecate import deprecate + from pyiron_base.storage.datacontainer import DataContainer -from pyiron_base.utils.deprecate import deprecate class InputList(DataContainer): diff --git a/pyiron_base/utils/deprecate.py b/pyiron_base/utils/deprecate.py deleted file mode 100644 index f9a7bf913..000000000 --- a/pyiron_base/utils/deprecate.py +++ /dev/null @@ -1,195 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -""" -Utility functions used in pyiron. -In order to be accessible from anywhere in pyiron, they *must* remain free of any imports from pyiron! -""" - -from copy import copy -import functools -import types -import warnings - -__author__ = "Joerg Neugebauer, Jan Janssen" -__copyright__ = ( - "Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - " - "Computational Materials Design (CM) Department" -) -__version__ = "1.0" -__maintainer__ = "Jan Janssen" -__email__ = "janssen@mpie.de" -__status__ = "production" -__date__ = "Sep 1, 2017" - - -class Deprecator: - """ - Decorator class to mark functions and methods as deprecated with a uniform - warning message at the time the function is called. The message has the - form - - {function_name} is deprecated: {message}. It is not guaranteed to be in - service after {version}. - - unless `pending=True` was given. Then the message will be - - {function_name} will be deprecated in a future version: {message}. - - If message and version are not initialized or given during the decorating - call the respective parts are left out from the message. - - >>> deprecate = Deprecator() - >>> @deprecate - ... def foo(a, b): - ... pass - >>> foo(1, 2) - DeprecationWarning: __main__.foo is deprecated - - >>> @deprecate("use bar() instead") - ... def foo(a, b): - ... pass - >>> foo(1, 2) - DeprecationWarning: __main__.foo is deprecated: use bar instead - - >>> @deprecate("use bar() instead", version="0.4.0") - ... def foo(a, b): - ... pass - >>> foo(1, 2) - DeprecationWarning: __main__.foo is deprecated: use bar instead. It is not - guaranteed to be in service in vers. 0.4.0 - - >>> deprecate = Deprecator(message="pyiron says no!", version="0.5.0") - >>> @deprecate - ... def foo(a, b): - ... pass - >>> foo(1, 2) - DeprecationWarning: __main__.foo is deprecated: pyiron says no! It is not - guaranteed to be in service in vers. 0.5.0 - - Alternatively the decorator can also be called with `arguments` set to a dictionary mapping names of keyword - arguments to deprecation messages. In this case the warning will only be emitted when the decorated function is - called with arguments in that dictionary. - - >>> deprecate = Deprecator() - >>> @deprecate(arguments={"bar": "use baz instead."}) - ... def foo(bar=None, baz=None): - ... pass - >>> foo(baz=True) - >>> foo(bar=True) - DeprecationWarning: __main__.foo(bar=True) is deprecated: use baz instead. - - As a short cut, it is also possible to pass the values in the arguments dict directly as keyword arguments to the - decorator. - - >>> @deprecate(bar="use baz instead.") - ... def foo(bar=None, baz=None): - ... pass - >>> foo(baz=True) - >>> foo(bar=True) - DeprecationWarning: __main__.foo(bar=True) is deprecated: use baz instead. - """ - - def __init__(self, message=None, version=None, pending=False): - """ - Initialize default values for deprecation message and version. - - Args: - message (str): default deprecation message - version (str): default version after which the function might be removed - pending (bool): only warn about future deprecation, warning category will be PendingDeprecationWarning - instead of DeprecationWarning - """ - self.message = message - self.version = version - self.category = PendingDeprecationWarning if pending else DeprecationWarning - - def __copy__(self): - cp = type(self)(message=self.message, version=self.version) - cp.category = self.category - return cp - - def __call__(self, message=None, version=None, arguments=None, **kwargs): - depr = copy(self) - if isinstance(message, types.FunctionType): - return depr.__deprecate_function(message) - else: - depr.message = message - depr.version = version - depr.arguments = arguments if arguments is not None else {} - depr.arguments.update(kwargs) - return depr.wrap - - def _build_message(self): - if self.category == PendingDeprecationWarning: - message_format = "{} will be deprecated" - else: - message_format = "{} is deprecated" - - if self.message is not None: - message_format += ": {}.".format(self.message) - else: - message_format += "." - - if self.version is not None: - message_format += ( - " It is not guaranteed to be in service in vers. {}".format( - self.version - ) - ) - - return message_format - - def __deprecate_function(self, function): - message = self._build_message().format( - "{}.{}".format(function.__module__, function.__name__) - ) - - @functools.wraps(function) - def decorated(*args, **kwargs): - warnings.warn(message, category=self.category, stacklevel=2) - return function(*args, **kwargs) - - return decorated - - def __deprecate_argument(self, function): - message_format = self._build_message() - - @functools.wraps(function) - def decorated(*args, **kwargs): - for kw in kwargs: - if kw in self.arguments: - warnings.warn( - message_format.format( - "{}.{}({}={})".format( - function.__module__, function.__name__, kw, kwargs[kw] - ) - ), - category=self.category, - stacklevel=2, - ) - return function(*args, **kwargs) - - return decorated - - def wrap(self, function): - """ - Wrap the given function to emit a DeprecationWarning at call time. The warning message is constructed from the - given message and version. If :attr:`.arguments` is set then the warning is only emitted, when the decorated - function is called with keyword arguments found in that dictionary. - - Args: - function (function): function to mark as deprecated - - Return: - function: raises DeprecationWarning when given function is called - """ - if not self.arguments: - return self.__deprecate_function(function) - else: - return self.__deprecate_argument(function) - - -deprecate = Deprecator() -deprecate_soon = Deprecator(pending=True) diff --git a/tests/generic/test_util.py b/tests/generic/test_util.py index b31139bfc..87c70db84 100644 --- a/tests/generic/test_util.py +++ b/tests/generic/test_util.py @@ -5,7 +5,6 @@ import unittest import warnings from pyiron_base.utils.instance import static_isinstance -from pyiron_base.utils.deprecate import deprecate, deprecate_soon from pyiron_base._tests import PyironTestCase @@ -27,122 +26,5 @@ def test_static_isinstance(self): self.assertRaises(TypeError, static_isinstance, list(), 1) -class TestDeprecator(PyironTestCase): - def test_deprecate(self): - """Function decorated with `deprecate` should raise a warning.""" - - @deprecate - def foo(a): - return 2 * a - - @deprecate("use baz instead", version="0.2.0") - def bar(a): - return 4 * a - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - foo(1), 2, "Decorated function does not return original " "return value" - ) - self.assertTrue(len(w) > 0, "No warning raised!") - self.assertEqual( - w[0].category, - DeprecationWarning, - "Raised warning is not a DeprecationWarning", - ) - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - bar(1), 4, "Decorated function does not return original " "return value" - ) - - expected_message = ( - "use baz instead. It is not guaranteed to be in " "service in vers. 0.2.0" - ) - self.assertTrue( - w[0].message.args[0].endswith(expected_message), - "Warning message does not reflect decorator arguments.", - ) - - @deprecate_soon - def baz(a): - return 3 * a - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - baz(1), 3, "Decorated function does not return original " "return value" - ) - self.assertEqual( - w[0].category, - PendingDeprecationWarning, - "Raised warning is not a PendingDeprecationWarning", - ) - - def test_deprecate_args(self): - """DeprecationWarning should only be raised when the given arguments occur.""" - - @deprecate(arguments={"bar": "use foo instead"}) - def foo(a, foo=None, bar=None): - return 2 * a - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - foo(1, bar=True), - 2, - "Decorated function does not return original " "return value", - ) - self.assertTrue(len(w) > 0, "No warning raised!") - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - foo(1, foo=True), - 2, - "Decorated function does not return original " "return value", - ) - self.assertEqual( - len(w), 0, "Warning raised, but deprecated argument was not given." - ) - - def test_deprecate_kwargs(self): - """DeprecationWarning should only be raised when the given arguments occur, also when given via kwargs.""" - - @deprecate(bar="use baz instead") - def foo(a, bar=None, baz=None): - return 2 * a - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - foo(1, bar=True), - 2, - "Decorated function does not return original " "return value", - ) - self.assertTrue(len(w) > 0, "No warning raised!") - - with warnings.catch_warnings(record=True) as w: - self.assertEqual( - foo(1, baz=True), - 2, - "Decorated function does not return original " "return value", - ) - self.assertEqual( - len(w), 0, "Warning raised, but deprecated argument was not given." - ) - - def test_instances(self): - """Subsequent calls to a Deprecator instance must not interfere with each other.""" - - @deprecate(bar="use baz instead") - def foo(bar=None, baz=None): - pass - - @deprecate(baz="use bar instead") - def food(bar=None, baz=None): - pass - - with warnings.catch_warnings(record=True) as w: - foo(bar=True) - food(baz=True) - self.assertEqual(len(w), 2, "Not all warnings preserved.") - - if __name__ == "__main__": unittest.main() diff --git a/tests/state/test_logger.py b/tests/state/test_logger.py deleted file mode 100644 index 11589f957..000000000 --- a/tests/state/test_logger.py +++ /dev/null @@ -1,51 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -from unittest import TestCase -from pyiron_base.state.logger import logger -import os -import shutil - - -class TestLogger(TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.logger_file = os.path.join(os.getcwd(), "pyiron.log") - cls.backup_file = os.path.join(os.getcwd(), "pyiron.log.test_logger_backup") - shutil.copy(cls.logger_file, cls.backup_file) - - @classmethod - def tearDownClass(cls) -> None: - shutil.move(cls.backup_file, cls.logger_file) - - def test_logger(self): - logsize = os.path.getsize(self.logger_file) - logger.warning("Here is a warning") - self.assertGreater(os.path.getsize(self.logger_file), logsize) - - def test_set_logging_level(self): - logger.set_logging_level(10) - self.assertEqual( - 10, logger.getEffectiveLevel(), "Overall logger level should match input" - ) - self.assertEqual( - 10, logger.handlers[0].level, "Stream level should match input" - ) - self.assertEqual(10, logger.handlers[0].level, "File level should match input") - - logger.set_logging_level(20, channel=1) - self.assertEqual( - 10, - logger.getEffectiveLevel(), - "Overall logger level should not have changed", - ) - self.assertEqual( - 10, logger.handlers[0].level, "Stream level should not have changed" - ) - self.assertEqual(20, logger.handlers[1].level, "File level should match input") - - logger.set_logging_level("WARNING", channel=0) - self.assertEqual( - 30, logger.handlers[0].level, "Should be able to set by string" - )