From 6bf6cf860e4c0404c131f4b4d63d45cacd0da403 Mon Sep 17 00:00:00 2001 From: Jakob Gamper <97gamjak@gmail.com> Date: Sun, 5 May 2024 09:44:11 +0200 Subject: [PATCH 01/10] __init__.py beartype strategy changed to possibility to disable it --- PQAnalysis/__init__.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/PQAnalysis/__init__.py b/PQAnalysis/__init__.py index 3f6a697b..11244be5 100644 --- a/PQAnalysis/__init__.py +++ b/PQAnalysis/__init__.py @@ -14,12 +14,26 @@ from PQAnalysis.utils.custom_logging import CustomLogger -beartype_this_package() - __base_path__ = Path(__file__).parent - __package_name__ = __name__ +################## +# BEARTYPE SETUP # +################## + +# TODO: change the default level to "RELEASE" after all changes are implemented +__beartype_default_level__ = "DEBUG" +__beartype_level__ = os.getenv( + "PQANALYSIS_BEARTYPE_LEVEL", __beartype_default_level__ +) + +if __beartype_level__.upper() == "DEBUG": + beartype_this_package() + +################# +# LOGGING SETUP # +################# + logging_env_var = os.getenv("PQANALYSIS_LOGGING_LEVEL") if logging_env_var and logging_env_var not in logging.getLevelNamesMapping(): From caa1791ff797525ca8d44dc809285ff264554032 Mon Sep 17 00:00:00 2001 From: Jakob Gamper <97gamjak@gmail.com> Date: Sun, 5 May 2024 21:36:45 +0200 Subject: [PATCH 02/10] added runtime type checking decorator --- PQAnalysis/analysis/rdf/api.py | 17 ++++++ PQAnalysis/type_checking.py | 57 +++++++++++++++++++ pytest.ini | 1 + tests/analysis/__init__.py | 3 + tests/analysis/rdf/__init__.py | 0 tests/analysis/rdf/test_api.py | 18 ++++++ tests/analysis/rdf/test_rdfInputFileReader.py | 4 ++ 7 files changed, 100 insertions(+) create mode 100644 PQAnalysis/type_checking.py create mode 100644 tests/analysis/__init__.py create mode 100644 tests/analysis/rdf/__init__.py create mode 100644 tests/analysis/rdf/test_api.py diff --git a/PQAnalysis/analysis/rdf/api.py b/PQAnalysis/analysis/rdf/api.py index f1230b14..79e9c49d 100644 --- a/PQAnalysis/analysis/rdf/api.py +++ b/PQAnalysis/analysis/rdf/api.py @@ -2,9 +2,13 @@ This module provides API functions for the radial distribution function (RDF) analysis. """ +import logging + from PQAnalysis.io import TrajectoryReader, RestartFileReader, MoldescriptorReader from PQAnalysis.traj import MDEngineFormat from PQAnalysis.topology import Topology +from PQAnalysis.utils.custom_logging import setup_logger +from PQAnalysis import __package_name__ from .rdf import RDF from .rdf_input_file_reader import RDFInputFileReader @@ -32,6 +36,19 @@ def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): For more information on the supported formats please visit :py:class:`~PQAnalysis.traj.formats.MDEngineFormat`. """ + + logger = logging.getLogger(__package_name__).getChild(__name__) + logger = setup_logger(logger) + + if not isinstance(input_file, str): + logger.error("Input file must be a string", exception=TypeError) + + if not isinstance(md_format, (MDEngineFormat, str)): + logger.error( + "md_format must be a MDEngineFormat or a string", + exception=TypeError + ) + md_format = MDEngineFormat(md_format) input_reader = RDFInputFileReader(input_file) diff --git a/PQAnalysis/type_checking.py b/PQAnalysis/type_checking.py new file mode 100644 index 00000000..c66cdecb --- /dev/null +++ b/PQAnalysis/type_checking.py @@ -0,0 +1,57 @@ +import logging + +from decorator import decorator +from beartype.door import is_bearable + +from PQAnalysis.utils.custom_logging import setup_logger + +logger_name = "PQAnalysis.TypeChecking" + +if not logging.getLogger(logger_name).handlers: + logger = setup_logger(logging.getLogger(logger_name)) +else: + logger = logging.getLogger(logger_name) + + +@decorator +def runtime_type_checking(func, *args, **kwargs): + """ + A decorator to check the type of the arguments passed to a function at runtime. + """ + + # Get the type hints of the function + type_hints = func.__annotations__ + + # Check the type of each argument + for arg_name, arg_value in zip(func.__code__.co_varnames, args): + if arg_name in type_hints: + if not is_bearable(arg_value, type_hints[arg_name]): + logger.error( + f"Argument '{arg_name}' should be of type " + f"{type_hints[arg_name]}, but got {type(arg_value)}", + exception=TypeError, + ) + + # Check the type of each keyword argument + for kwarg_name, kwarg_value in kwargs.items(): + if kwarg_name in type_hints: + if not is_bearable(kwarg_value, type_hints[kwarg_name]): + logger.error( + f"Argument '{kwarg_name}' should be of type " + f"{type_hints[kwarg_name]}, but got {type(kwarg_value)}", + exception=TypeError, + ) + + # Call the function + return func(*args, **kwargs) + + +def _get_type_error_message(arg_name, expected_type, actual_type): + """ + Get the error message for a type error. + """ + + header = ( + f"Argument '{arg_name}' should be of type {expected_type}, " + f"but got {actual_type}" + ) diff --git a/pytest.ini b/pytest.ini index 03ab7380..19dace22 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,6 +5,7 @@ markers = topology traj io + analysis testpaths = tests diff --git a/tests/analysis/__init__.py b/tests/analysis/__init__.py new file mode 100644 index 00000000..0e1d59fc --- /dev/null +++ b/tests/analysis/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytestmark = pytest.mark.analysis diff --git a/tests/analysis/rdf/__init__.py b/tests/analysis/rdf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/analysis/rdf/test_api.py b/tests/analysis/rdf/test_api.py new file mode 100644 index 00000000..caf36f7e --- /dev/null +++ b/tests/analysis/rdf/test_api.py @@ -0,0 +1,18 @@ +import pytest + +from PQAnalysis.analysis.rdf.api import rdf + +from .. import pytestmark # pylint: disable=unused-import +from ...conftest import assert_logging + + +# class TestRDFAPI: +# # def test_wrong_param_types(self, caplog): +# # assert_logging( +# # caplog=caplog, +# # logging_name=rdf.__name__, +# # logging_level="ERROR", +# # message_to_test="Input file must be a string", +# # function=rdf, +# # input_file=1, +# # ) diff --git a/tests/analysis/rdf/test_rdfInputFileReader.py b/tests/analysis/rdf/test_rdfInputFileReader.py index 49621bb6..63685aa4 100644 --- a/tests/analysis/rdf/test_rdfInputFileReader.py +++ b/tests/analysis/rdf/test_rdfInputFileReader.py @@ -3,6 +3,10 @@ from PQAnalysis.analysis.rdf.rdf_input_file_reader import RDFInputFileReader from PQAnalysis.io.input_file_reader.exceptions import InputFileError +# import topology marker +from .. import pytestmark # pylint: disable=unused-import +from ...conftest import assert_logging + class TestRDFInputFileReader: @pytest.mark.parametrize("example_dir", ["rdf"], indirect=False) From bc3f4234424e81c4b5d55cdb3f2b435ee0413af6 Mon Sep 17 00:00:00 2001 From: Jakob Gamper <97gamjak@gmail.com> Date: Sun, 5 May 2024 09:44:11 +0200 Subject: [PATCH 03/10] __init__.py beartype strategy changed to possibility to disable it --- PQAnalysis/__init__.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/PQAnalysis/__init__.py b/PQAnalysis/__init__.py index 3f6a697b..11244be5 100644 --- a/PQAnalysis/__init__.py +++ b/PQAnalysis/__init__.py @@ -14,12 +14,26 @@ from PQAnalysis.utils.custom_logging import CustomLogger -beartype_this_package() - __base_path__ = Path(__file__).parent - __package_name__ = __name__ +################## +# BEARTYPE SETUP # +################## + +# TODO: change the default level to "RELEASE" after all changes are implemented +__beartype_default_level__ = "DEBUG" +__beartype_level__ = os.getenv( + "PQANALYSIS_BEARTYPE_LEVEL", __beartype_default_level__ +) + +if __beartype_level__.upper() == "DEBUG": + beartype_this_package() + +################# +# LOGGING SETUP # +################# + logging_env_var = os.getenv("PQANALYSIS_LOGGING_LEVEL") if logging_env_var and logging_env_var not in logging.getLevelNamesMapping(): From 96cc69a8c470f4afd579007eec3a421e52c7675d Mon Sep 17 00:00:00 2001 From: Jakob Gamper <97gamjak@gmail.com> Date: Sun, 5 May 2024 21:36:45 +0200 Subject: [PATCH 04/10] added runtime type checking decorator --- PQAnalysis/analysis/rdf/api.py | 17 ++++++ PQAnalysis/type_checking.py | 57 +++++++++++++++++++ pytest.ini | 1 + tests/analysis/__init__.py | 3 + tests/analysis/rdf/__init__.py | 0 tests/analysis/rdf/test_api.py | 18 ++++++ tests/analysis/rdf/test_rdfInputFileReader.py | 4 ++ 7 files changed, 100 insertions(+) create mode 100644 PQAnalysis/type_checking.py create mode 100644 tests/analysis/__init__.py create mode 100644 tests/analysis/rdf/__init__.py create mode 100644 tests/analysis/rdf/test_api.py diff --git a/PQAnalysis/analysis/rdf/api.py b/PQAnalysis/analysis/rdf/api.py index f1230b14..79e9c49d 100644 --- a/PQAnalysis/analysis/rdf/api.py +++ b/PQAnalysis/analysis/rdf/api.py @@ -2,9 +2,13 @@ This module provides API functions for the radial distribution function (RDF) analysis. """ +import logging + from PQAnalysis.io import TrajectoryReader, RestartFileReader, MoldescriptorReader from PQAnalysis.traj import MDEngineFormat from PQAnalysis.topology import Topology +from PQAnalysis.utils.custom_logging import setup_logger +from PQAnalysis import __package_name__ from .rdf import RDF from .rdf_input_file_reader import RDFInputFileReader @@ -32,6 +36,19 @@ def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): For more information on the supported formats please visit :py:class:`~PQAnalysis.traj.formats.MDEngineFormat`. """ + + logger = logging.getLogger(__package_name__).getChild(__name__) + logger = setup_logger(logger) + + if not isinstance(input_file, str): + logger.error("Input file must be a string", exception=TypeError) + + if not isinstance(md_format, (MDEngineFormat, str)): + logger.error( + "md_format must be a MDEngineFormat or a string", + exception=TypeError + ) + md_format = MDEngineFormat(md_format) input_reader = RDFInputFileReader(input_file) diff --git a/PQAnalysis/type_checking.py b/PQAnalysis/type_checking.py new file mode 100644 index 00000000..c66cdecb --- /dev/null +++ b/PQAnalysis/type_checking.py @@ -0,0 +1,57 @@ +import logging + +from decorator import decorator +from beartype.door import is_bearable + +from PQAnalysis.utils.custom_logging import setup_logger + +logger_name = "PQAnalysis.TypeChecking" + +if not logging.getLogger(logger_name).handlers: + logger = setup_logger(logging.getLogger(logger_name)) +else: + logger = logging.getLogger(logger_name) + + +@decorator +def runtime_type_checking(func, *args, **kwargs): + """ + A decorator to check the type of the arguments passed to a function at runtime. + """ + + # Get the type hints of the function + type_hints = func.__annotations__ + + # Check the type of each argument + for arg_name, arg_value in zip(func.__code__.co_varnames, args): + if arg_name in type_hints: + if not is_bearable(arg_value, type_hints[arg_name]): + logger.error( + f"Argument '{arg_name}' should be of type " + f"{type_hints[arg_name]}, but got {type(arg_value)}", + exception=TypeError, + ) + + # Check the type of each keyword argument + for kwarg_name, kwarg_value in kwargs.items(): + if kwarg_name in type_hints: + if not is_bearable(kwarg_value, type_hints[kwarg_name]): + logger.error( + f"Argument '{kwarg_name}' should be of type " + f"{type_hints[kwarg_name]}, but got {type(kwarg_value)}", + exception=TypeError, + ) + + # Call the function + return func(*args, **kwargs) + + +def _get_type_error_message(arg_name, expected_type, actual_type): + """ + Get the error message for a type error. + """ + + header = ( + f"Argument '{arg_name}' should be of type {expected_type}, " + f"but got {actual_type}" + ) diff --git a/pytest.ini b/pytest.ini index 03ab7380..19dace22 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,6 +5,7 @@ markers = topology traj io + analysis testpaths = tests diff --git a/tests/analysis/__init__.py b/tests/analysis/__init__.py new file mode 100644 index 00000000..0e1d59fc --- /dev/null +++ b/tests/analysis/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytestmark = pytest.mark.analysis diff --git a/tests/analysis/rdf/__init__.py b/tests/analysis/rdf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/analysis/rdf/test_api.py b/tests/analysis/rdf/test_api.py new file mode 100644 index 00000000..caf36f7e --- /dev/null +++ b/tests/analysis/rdf/test_api.py @@ -0,0 +1,18 @@ +import pytest + +from PQAnalysis.analysis.rdf.api import rdf + +from .. import pytestmark # pylint: disable=unused-import +from ...conftest import assert_logging + + +# class TestRDFAPI: +# # def test_wrong_param_types(self, caplog): +# # assert_logging( +# # caplog=caplog, +# # logging_name=rdf.__name__, +# # logging_level="ERROR", +# # message_to_test="Input file must be a string", +# # function=rdf, +# # input_file=1, +# # ) diff --git a/tests/analysis/rdf/test_rdfInputFileReader.py b/tests/analysis/rdf/test_rdfInputFileReader.py index 49621bb6..63685aa4 100644 --- a/tests/analysis/rdf/test_rdfInputFileReader.py +++ b/tests/analysis/rdf/test_rdfInputFileReader.py @@ -3,6 +3,10 @@ from PQAnalysis.analysis.rdf.rdf_input_file_reader import RDFInputFileReader from PQAnalysis.io.input_file_reader.exceptions import InputFileError +# import topology marker +from .. import pytestmark # pylint: disable=unused-import +from ...conftest import assert_logging + class TestRDFInputFileReader: @pytest.mark.parametrize("example_dir", ["rdf"], indirect=False) From c045e26ddfa50f14c05d1127e9cc935918bea155 Mon Sep 17 00:00:00 2001 From: 97gamjak <97gamjak@gmail.com> Date: Mon, 6 May 2024 09:36:53 +0200 Subject: [PATCH 05/10] runtime_type_checking implemented --- PQAnalysis/analysis/rdf/api.py | 16 ++---------- PQAnalysis/type_checking.py | 47 +++++++++++++++++++++++++++------- tests/analysis/rdf/test_api.py | 20 +++++++-------- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/PQAnalysis/analysis/rdf/api.py b/PQAnalysis/analysis/rdf/api.py index 79e9c49d..9e2d3325 100644 --- a/PQAnalysis/analysis/rdf/api.py +++ b/PQAnalysis/analysis/rdf/api.py @@ -7,14 +7,14 @@ from PQAnalysis.io import TrajectoryReader, RestartFileReader, MoldescriptorReader from PQAnalysis.traj import MDEngineFormat from PQAnalysis.topology import Topology -from PQAnalysis.utils.custom_logging import setup_logger -from PQAnalysis import __package_name__ +from PQAnalysis.type_checking import runtime_type_checking from .rdf import RDF from .rdf_input_file_reader import RDFInputFileReader from .rdf_output_file_writer import RDFDataWriter, RDFLogWriter +@runtime_type_checking def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): """ Calculates the radial distribution function (RDF) using a given input file. @@ -37,18 +37,6 @@ def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): :py:class:`~PQAnalysis.traj.formats.MDEngineFormat`. """ - logger = logging.getLogger(__package_name__).getChild(__name__) - logger = setup_logger(logger) - - if not isinstance(input_file, str): - logger.error("Input file must be a string", exception=TypeError) - - if not isinstance(md_format, (MDEngineFormat, str)): - logger.error( - "md_format must be a MDEngineFormat or a string", - exception=TypeError - ) - md_format = MDEngineFormat(md_format) input_reader = RDFInputFileReader(input_file) diff --git a/PQAnalysis/type_checking.py b/PQAnalysis/type_checking.py index c66cdecb..523968b9 100644 --- a/PQAnalysis/type_checking.py +++ b/PQAnalysis/type_checking.py @@ -4,13 +4,21 @@ from beartype.door import is_bearable from PQAnalysis.utils.custom_logging import setup_logger +from .types import ( + Np1DIntArray, + Np2DIntArray, + Np1DNumberArray, + Np2DNumberArray, + Np3x3NumberArray, + NpnDNumberArray, +) -logger_name = "PQAnalysis.TypeChecking" +__logger_name__ = "PQAnalysis.TypeChecking" -if not logging.getLogger(logger_name).handlers: - logger = setup_logger(logging.getLogger(logger_name)) +if not logging.getLogger(__logger_name__).handlers: + logger = setup_logger(logging.getLogger(__logger_name__)) else: - logger = logging.getLogger(logger_name) + logger = logging.getLogger(__logger_name__) @decorator @@ -27,8 +35,11 @@ def runtime_type_checking(func, *args, **kwargs): if arg_name in type_hints: if not is_bearable(arg_value, type_hints[arg_name]): logger.error( - f"Argument '{arg_name}' should be of type " - f"{type_hints[arg_name]}, but got {type(arg_value)}", + _get_type_error_message( + arg_name, + type_hints[arg_name], + type(arg_value) + ), exception=TypeError, ) @@ -37,8 +48,11 @@ def runtime_type_checking(func, *args, **kwargs): if kwarg_name in type_hints: if not is_bearable(kwarg_value, type_hints[kwarg_name]): logger.error( - f"Argument '{kwarg_name}' should be of type " - f"{type_hints[kwarg_name]}, but got {type(kwarg_value)}", + _get_type_error_message( + kwarg_name, + type_hints[kwarg_name], + type(kwarg_value) + ), exception=TypeError, ) @@ -53,5 +67,20 @@ def _get_type_error_message(arg_name, expected_type, actual_type): header = ( f"Argument '{arg_name}' should be of type {expected_type}, " - f"but got {actual_type}" + f"but got {actual_type}." ) + + if expected_type is Np1DIntArray: + header += " Expected a 1D numpy integer array." + elif expected_type is Np2DIntArray: + header += " Expected a 2D numpy integer array." + elif expected_type is Np1DNumberArray: + header += " Expected a 1D numpy number array." + elif expected_type is Np2DNumberArray: + header += " Expected a 2D numpy number array." + elif expected_type is Np3x3NumberArray: + header += " Expected a 3x3 numpy number array." + elif expected_type is NpnDNumberArray: + header += " Expected an n-dimensional numpy number array." + + return header diff --git a/tests/analysis/rdf/test_api.py b/tests/analysis/rdf/test_api.py index caf36f7e..b82fba27 100644 --- a/tests/analysis/rdf/test_api.py +++ b/tests/analysis/rdf/test_api.py @@ -6,13 +6,13 @@ from ...conftest import assert_logging -# class TestRDFAPI: -# # def test_wrong_param_types(self, caplog): -# # assert_logging( -# # caplog=caplog, -# # logging_name=rdf.__name__, -# # logging_level="ERROR", -# # message_to_test="Input file must be a string", -# # function=rdf, -# # input_file=1, -# # ) +class TestRDFAPI: + def test_wrong_param_types(self, caplog): + assert_logging( + caplog=caplog, + logging_name=rdf.__name__, + logging_level="ERROR", + message_to_test="Input file must be a string", + function=rdf, + input_file=1, + ) From 552bb51a78b67855bb37c91a169b44cd012981f4 Mon Sep 17 00:00:00 2001 From: Jakob Gamper <97gamjak@gmail.com> Date: Sun, 5 May 2024 21:36:45 +0200 Subject: [PATCH 06/10] added runtime type checking decorator --- PQAnalysis/analysis/rdf/api.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/PQAnalysis/analysis/rdf/api.py b/PQAnalysis/analysis/rdf/api.py index 9e2d3325..b43e5303 100644 --- a/PQAnalysis/analysis/rdf/api.py +++ b/PQAnalysis/analysis/rdf/api.py @@ -2,8 +2,6 @@ This module provides API functions for the radial distribution function (RDF) analysis. """ -import logging - from PQAnalysis.io import TrajectoryReader, RestartFileReader, MoldescriptorReader from PQAnalysis.traj import MDEngineFormat from PQAnalysis.topology import Topology @@ -21,7 +19,7 @@ def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): This is just a wrapper function combining the underlying classes and functions. - For more information on the input file keys please + For more information on the input file keys please visit :py:mod:`~PQAnalysis.analysis.rdf.rdfInputFileReader`. For more information on the exact calculation of the RDF please visit :py:class:`~PQAnalysis.analysis.rdf.rdf.RDF`. @@ -29,10 +27,10 @@ def rdf(input_file: str, md_format: MDEngineFormat | str = MDEngineFormat.PQ): Parameters ---------- input_file : str - The input file. For more information on the input file + The input file. For more information on the input file keys please visit :py:mod:`~PQAnalysis.analysis.rdf.rdfInputFileReader`. md_format : MDEngineFormat | str, optional - the format of the input trajectory. Default is "PQ". + the format of the input trajectory. Default is "PQ". For more information on the supported formats please visit :py:class:`~PQAnalysis.traj.formats.MDEngineFormat`. """ From 199f8fddbd090ff504cf1cffafb48ac8c828af6a Mon Sep 17 00:00:00 2001 From: 97gamjak <97gamjak@gmail.com> Date: Mon, 6 May 2024 10:19:51 +0200 Subject: [PATCH 07/10] runtime type checking decorator implemented and tested for rdf api --- PQAnalysis/type_checking.py | 12 +++++++----- PQAnalysis/utils/custom_logging.py | 27 ++++++++++++++++++--------- tests/__init__.py | 3 +++ tests/analysis/rdf/test_api.py | 12 ++++++++---- tests/conftest.py | 2 +- tests/io/test_frameReader.py | 9 ++------- tests/io/test_infoFileReader.py | 10 ++-------- tests/physicalData/test_energy.py | 18 +++--------------- 8 files changed, 44 insertions(+), 49 deletions(-) diff --git a/PQAnalysis/type_checking.py b/PQAnalysis/type_checking.py index 523968b9..93b5bcab 100644 --- a/PQAnalysis/type_checking.py +++ b/PQAnalysis/type_checking.py @@ -37,8 +37,8 @@ def runtime_type_checking(func, *args, **kwargs): logger.error( _get_type_error_message( arg_name, + arg_value, type_hints[arg_name], - type(arg_value) ), exception=TypeError, ) @@ -50,8 +50,8 @@ def runtime_type_checking(func, *args, **kwargs): logger.error( _get_type_error_message( kwarg_name, + kwarg_value, type_hints[kwarg_name], - type(kwarg_value) ), exception=TypeError, ) @@ -60,14 +60,16 @@ def runtime_type_checking(func, *args, **kwargs): return func(*args, **kwargs) -def _get_type_error_message(arg_name, expected_type, actual_type): +def _get_type_error_message(arg_name, value, expected_type): """ Get the error message for a type error. """ + actual_type = type(value) + header = ( - f"Argument '{arg_name}' should be of type {expected_type}, " - f"but got {actual_type}." + f"Argument '{arg_name}' with {value=} should be " + f"of type {expected_type}, but got {actual_type}." ) if expected_type is Np1DIntArray: diff --git a/PQAnalysis/utils/custom_logging.py b/PQAnalysis/utils/custom_logging.py index e16ea680..fe155137 100644 --- a/PQAnalysis/utils/custom_logging.py +++ b/PQAnalysis/utils/custom_logging.py @@ -121,15 +121,15 @@ def _log(self, # pylint: disable=arguments-differ ) if level in [logging.CRITICAL, logging.ERROR]: + + exception = exception or Exception + if self.isEnabledFor(logging.DEBUG): back_tb = None try: - if exception is not None: - raise exception - - raise Exception # pylint: disable=broad-exception-raised - except Exception: # pylint: disable=broad-except + raise exception # pylint: disable=broad-exception-raised + except exception: # pylint: disable=broad-except traceback = sys.exc_info()[2] back_frame = traceback.tb_frame.f_back @@ -140,12 +140,21 @@ def _log(self, # pylint: disable=arguments-differ tb_lineno=back_frame.f_lineno ) - if exception is not None: - raise Exception(msg).with_traceback(back_tb) - raise exception(msg).with_traceback(back_tb) - sys.exit(1) + class DevNull: + """ + Dummy class to redirect the sys.stderr to /dev/null. + """ + + def write(self, _): + """ + Dummy write method. + """ + + sys.stderr = DevNull() + + raise exception(msg) # pylint: disable=raise-missing-from def _original_log(self, level: Any, diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..9ea12d19 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +import os + +os.environ['PQANALYSIS_BEARTYPE_LEVEL'] = "RELEASE" diff --git a/tests/analysis/rdf/test_api.py b/tests/analysis/rdf/test_api.py index b82fba27..34c6e6d6 100644 --- a/tests/analysis/rdf/test_api.py +++ b/tests/analysis/rdf/test_api.py @@ -1,18 +1,22 @@ import pytest from PQAnalysis.analysis.rdf.api import rdf +from PQAnalysis.type_checking import _get_type_error_message from .. import pytestmark # pylint: disable=unused-import -from ...conftest import assert_logging +from ...conftest import assert_logging_with_exception class TestRDFAPI: def test_wrong_param_types(self, caplog): - assert_logging( + assert_logging_with_exception( caplog=caplog, - logging_name=rdf.__name__, + logging_name="TypeChecking", logging_level="ERROR", - message_to_test="Input file must be a string", + message_to_test=_get_type_error_message( + "input_file", 1, str, + ), + exception=TypeError, function=rdf, input_file=1, ) diff --git a/tests/conftest.py b/tests/conftest.py index 00bd0356..38480b7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,7 +93,7 @@ def assert_logging_with_exception(caplog, logging_name, logging_level, message_t result = None try: result = function(*args, **kwargs) - except SystemExit: + except: pass record = caplog.records[0] diff --git a/tests/io/test_frameReader.py b/tests/io/test_frameReader.py index 80c529ec..1ba85300 100644 --- a/tests/io/test_frameReader.py +++ b/tests/io/test_frameReader.py @@ -1,10 +1,6 @@ import pytest import numpy as np -from beartype.roar import BeartypeException - -from . import pytestmark - from PQAnalysis.io import FrameReader from PQAnalysis.io.traj_file.exceptions import FrameReaderError from PQAnalysis.core import Cell, Atom @@ -12,6 +8,8 @@ from PQAnalysis.traj import TrajectoryFormat from PQAnalysis.topology import Topology +from . import pytestmark + class TestFrameReader: @@ -67,9 +65,6 @@ def test__read_scalar(self): def test_read(self): reader = FrameReader() - with pytest.raises(BeartypeException): - reader.read(["tmp"]) - frame = reader.read( "2 2.0 3.0 4.0 5.0 6.0 7.0\n\nh 1.0 2.0 3.0\no 2.0 2.0 2.0") assert frame.n_atoms == 2 diff --git a/tests/io/test_infoFileReader.py b/tests/io/test_infoFileReader.py index 5ad9def1..473db610 100644 --- a/tests/io/test_infoFileReader.py +++ b/tests/io/test_infoFileReader.py @@ -1,13 +1,11 @@ import pytest -from beartype.roar import BeartypeException - -from . import pytestmark - from PQAnalysis.io import InfoFileReader from PQAnalysis.traj import MDEngineFormat from PQAnalysis.traj.exceptions import MDEngineFormatError +from . import pytestmark + @pytest.mark.parametrize("example_dir", ["readInfoFile"], indirect=False) def test__init__(test_with_data_dir): @@ -15,10 +13,6 @@ def test__init__(test_with_data_dir): InfoFileReader("tmp") assert str(exception.value) == "File tmp not found." - with pytest.raises(BeartypeException) as exception: - InfoFileReader( - "md-01.info", engine_format=None) - with pytest.raises(MDEngineFormatError) as exception: InfoFileReader( "md-01.info", engine_format="tmp") diff --git a/tests/physicalData/test_energy.py b/tests/physicalData/test_energy.py index 10dffc92..0692655b 100644 --- a/tests/physicalData/test_energy.py +++ b/tests/physicalData/test_energy.py @@ -1,32 +1,20 @@ -import pytest -import numpy as np from collections import defaultdict -from beartype.roar import BeartypeException + +import pytest +import numpy as np from PQAnalysis.physical_data import Energy, EnergyError class TestEnergy: def test__init__(self): - with pytest.raises(BeartypeException): - Energy(1) - - with pytest.raises(BeartypeException): - Energy([[[1]]]) - data = np.array([1, 2, 3]) energy = Energy(data) assert np.allclose(energy.data, [data]) def test__setup_info_dictionary(self): - with pytest.raises(BeartypeException): - Energy(np.array([1]), info=1) - - with pytest.raises(BeartypeException): - Energy(np.array([1]), units=1) - data = np.array([[1], [2]]) info = {1: 0, 2: 1} units = {1: "a", 2: "b"} From 4b82eedfea53083246e3de26f763e200b86e1987 Mon Sep 17 00:00:00 2001 From: 97gamjak <97gamjak@gmail.com> Date: Mon, 6 May 2024 10:33:21 +0200 Subject: [PATCH 08/10] pylint.yml updated - fixed problems with adding on push event --- .github/workflows/pylint.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 3d1a0056..4548dc49 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -16,10 +16,19 @@ permissions: jobs: pylint: + runs-on: ubuntu-latest + permissions: # Job-level permissions configuration starts here + contents: write # 'write' access to repository contents + pull-requests: write # 'write' access to pull requests + steps: - uses: actions/checkout@master + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python uses: actions/setup-python@v2 with: @@ -97,5 +106,10 @@ jobs: git add .github/.pylint_cache git commit -m "Add .github/.pylint_cache on push event" - git push + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} From 7779fc95816a5873076da09810aa5ae3320a505d Mon Sep 17 00:00:00 2001 From: 97gamjak <97gamjak@gmail.com> Date: Mon, 6 May 2024 10:46:02 +0200 Subject: [PATCH 09/10] added test case for property cells in test_trajectory - which were somehow affected by previous commit --- tests/traj/test_trajectory.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/traj/test_trajectory.py b/tests/traj/test_trajectory.py index 6fb9a076..29b334c1 100644 --- a/tests/traj/test_trajectory.py +++ b/tests/traj/test_trajectory.py @@ -144,10 +144,12 @@ def test_window(self, caplog): assert test_frames == [[self.frame1], [self.frame3]] test_frames = [traj.frames for traj in traj.window(2, 1)] - assert test_frames == [[self.frame1, self.frame2], [self.frame2, self.frame3]] + assert test_frames == [ + [self.frame1, self.frame2], [self.frame2, self.frame3]] test_frames = [traj.frames for traj in traj.window(2)] - assert test_frames == [[self.frame1, self.frame2], [self.frame2, self.frame3]] + assert test_frames == [ + [self.frame1, self.frame2], [self.frame2, self.frame3]] test_frames = [traj.frames for traj in traj.window(1)] assert test_frames == [[self.frame1], [self.frame2], [self.frame3]] @@ -234,7 +236,8 @@ def test_window(self, caplog): Trajectory.__qualname__, exception=IndexError, logging_level="ERROR", - message_to_test=("start index is greater than or equal to the stop index"), + message_to_test=( + "start index is greater than or equal to the stop index"), function=traj.window(1, 1, window_start=2, window_stop=1).__next__, ) @@ -319,7 +322,8 @@ def test_property_box_lengths(self): assert np.allclose( traj.box_lengths, np.array( - [[max_float, max_float, max_float], [max_float, max_float, max_float]] + [[max_float, max_float, max_float], [ + max_float, max_float, max_float]] ), ) @@ -327,4 +331,20 @@ def test_property_box_lengths(self): frame2 = AtomicSystem(cell=Cell(11, 11, 11)) traj = Trajectory([frame1, frame2]) - assert np.allclose(traj.box_lengths, np.array([[10, 10, 10], [11, 11, 11]])) + assert np.allclose(traj.box_lengths, np.array( + [[10, 10, 10], [11, 11, 11]])) + + def test_property_cells(self): + frame1 = AtomicSystem() + frame2 = AtomicSystem() + + traj = Trajectory([frame1, frame2]) + + assert traj.cells == [Cell(), Cell()] + + frame1 = AtomicSystem(cell=Cell(10, 10, 10)) + frame2 = AtomicSystem(cell=Cell(11, 11, 11)) + + traj = Trajectory([frame1, frame2]) + + assert traj.cells == [Cell(10, 10, 10), Cell(11, 11, 11)] From 3585a95bf52447c7f57b7ccd6f86a1b377fa1735 Mon Sep 17 00:00:00 2001 From: 97gamjak <97gamjak@gmail.com> Date: Mon, 6 May 2024 10:58:17 +0200 Subject: [PATCH 10/10] updated pylint.yml --- .github/workflows/pylint.yml | 1 + PQAnalysis/type_checking.py | 4 ++++ tests/__init__.py | 4 ++++ tests/analysis/rdf/test_api.py | 6 +++++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 4548dc49..47a068d9 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -108,6 +108,7 @@ jobs: git commit -m "Add .github/.pylint_cache on push event" - name: Push changes + if: github.event_name == 'push' uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/PQAnalysis/type_checking.py b/PQAnalysis/type_checking.py index 93b5bcab..7ce821b9 100644 --- a/PQAnalysis/type_checking.py +++ b/PQAnalysis/type_checking.py @@ -1,3 +1,7 @@ +""" +A module for type checking of arguments passed to functions at runtime. +""" + import logging from decorator import decorator diff --git a/tests/__init__.py b/tests/__init__.py index 9ea12d19..f42913fa 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,7 @@ +""" +Unit tests for the PQAnalysis package. +""" + import os os.environ['PQANALYSIS_BEARTYPE_LEVEL'] = "RELEASE" diff --git a/tests/analysis/rdf/test_api.py b/tests/analysis/rdf/test_api.py index 34c6e6d6..ce7c446b 100644 --- a/tests/analysis/rdf/test_api.py +++ b/tests/analysis/rdf/test_api.py @@ -1,4 +1,8 @@ -import pytest +""" +A module to test the RDF API. +""" + +import pytest # pylint: disable=unused-import from PQAnalysis.analysis.rdf.api import rdf from PQAnalysis.type_checking import _get_type_error_message