From 9c822c6848096033fb075a80f44a288500de8674 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Thu, 27 Apr 2023 05:14:07 -0300 Subject: [PATCH] Adds Functionality to Logger --- diceplayer/__init__.py | 4 + diceplayer/__main__.py | 8 +- diceplayer/shared/environment/system.py | 121 ++++++++++++------------ diceplayer/shared/utils/logger.py | 43 +++++++-- tests/shared/utils/test_logger.py | 88 ++++++++++++++--- 5 files changed, 177 insertions(+), 87 deletions(-) diff --git a/diceplayer/__init__.py b/diceplayer/__init__.py index e69de29..b2344e9 100644 --- a/diceplayer/__init__.py +++ b/diceplayer/__init__.py @@ -0,0 +1,4 @@ +from diceplayer.shared.utils.logger import Logger + + +logger = Logger(__name__) diff --git a/diceplayer/__main__.py b/diceplayer/__main__.py index 12fa4c3..5de9c32 100644 --- a/diceplayer/__main__.py +++ b/diceplayer/__main__.py @@ -1,5 +1,6 @@ from diceplayer.shared.interface.dice_interface import DiceInterface from diceplayer.player import Player +from diceplayer import logger from pathlib import Path import argparse @@ -40,6 +41,7 @@ def main(): args = parser.parse_args() # Open OUTFILE for writing and print keywords and initial info + logger.set_logger(args.outfile, logging.INFO) try: @@ -55,12 +57,6 @@ def main(): except Exception as err: sys.exit(err) - logging.basicConfig( - filename=args.outfile, - format='%(message)s', - level=logging.INFO - ) - player = Player(args.infile) player.start() diff --git a/diceplayer/shared/environment/system.py b/diceplayer/shared/environment/system.py index b34449b..1a6aff7 100644 --- a/diceplayer/shared/environment/system.py +++ b/diceplayer/shared/environment/system.py @@ -42,6 +42,29 @@ class System: raise TypeError("Error: nmols is not an integer") self.nmols.append(nmols) + def update_molecule(self, position: np.ndarray, fh: TextIO) -> None: + """Updates the position of the molecule in the Output file + + Args: + position (np.ndarray): numpy position vector + fh (TextIO): Output file + """ + + position_in_ang = (position * BOHR2ANG).tolist() + self.add_type(self.nmols[0], deepcopy(self.molecule[0])) + + for atom in self.molecule[-1].atom: + + atom.rx = position_in_ang.pop(0) + atom.ry = position_in_ang.pop(0) + atom.rz = position_in_ang.pop(0) + + rmsd, self.molecule[0] = self.rmsd_fit(-1, 0) + self.molecule.pop(-1) + + fh.write("\nProjected new conformation of reference molecule with RMSD fit\n") + fh.write("RMSD = {:>8.5f} Angstrom\n".format(rmsd)) + def rmsd_fit(self, p_index: int, r_index: int) -> Tuple[float, Molecule]: projecting_mol = self.molecule[p_index] @@ -176,65 +199,43 @@ class System: # self.molecule.pop(-1) # # return min_dist, nearestmol - def update_molecule(self, position: np.ndarray, fh: TextIO) -> None: - """Updates the position of the molecule in the Output file - - Args: - position (np.ndarray): numpy position vector - fh (TextIO): Output file - """ - - position_in_ang = (position * BOHR2ANG).tolist() - self.add_type(self.nmols[0], deepcopy(self.molecule[0])) - - for atom in self.molecule[-1].atom: - - atom.rx = position_in_ang.pop(0) - atom.ry = position_in_ang.pop(0) - atom.rz = position_in_ang.pop(0) - - rmsd, self.molecule[0] = self.rmsd_fit(-1, 0) - self.molecule.pop(-1) - - fh.write("\nProjected new conformation of reference molecule with RMSD fit\n") - fh.write("RMSD = {:>8.5f} Angstrom\n".format(rmsd)) - def print_geom(self, cycle: int, fh: TextIO) -> None: - """ - Print the geometry of the molecule in the Output file - - Args: - cycle (int): Number of the cycle - fh (TextIO): Output file - """ - - fh.write("Cycle # {}\n".format(cycle)) - fh.write("Number of site: {}\n".format(len(self.molecule[0].atom))) - for atom in self.molecule[0].atom: - symbol = atomsymb[atom.na] - fh.write( - "{:<2s} {:>10.6f} {:>10.6f} {:>10.6f}\n".format( - symbol, atom.rx, atom.ry, atom.rz - ) - ) - - def printChargesAndDipole(self, cycle: int, fh: TextIO) -> None: - """ - Print the charges and dipole of the molecule in the Output file - - Args: - cycle (int): Number of the cycle - fh (TextIO): Output file - """ - - fh.write("Cycle # {}\n".format(cycle)) - fh.write("Number of site: {}\n".format(len(self.molecule[0].atom))) - - chargesAndDipole = self.molecule[0].charges_and_dipole() - - fh.write( - "{:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f}\n".format( - chargesAndDipole[0], chargesAndDipole[1], chargesAndDipole[2], chargesAndDipole[3], chargesAndDipole[4] - ) - ) + # def print_geom(self, cycle: int, fh: TextIO) -> None: + # """ + # Print the geometry of the molecule in the Output file + # + # Args: + # cycle (int): Number of the cycle + # fh (TextIO): Output file + # """ + # + # fh.write("Cycle # {}\n".format(cycle)) + # fh.write("Number of site: {}\n".format(len(self.molecule[0].atom))) + # for atom in self.molecule[0].atom: + # symbol = atomsymb[atom.na] + # fh.write( + # "{:<2s} {:>10.6f} {:>10.6f} {:>10.6f}\n".format( + # symbol, atom.rx, atom.ry, atom.rz + # ) + # ) + # + # def printChargesAndDipole(self, cycle: int, fh: TextIO) -> None: + # """ + # Print the charges and dipole of the molecule in the Output file + # + # Args: + # cycle (int): Number of the cycle + # fh (TextIO): Output file + # """ + # + # fh.write("Cycle # {}\n".format(cycle)) + # fh.write("Number of site: {}\n".format(len(self.molecule[0].atom))) + # + # chargesAndDipole = self.molecule[0].charges_and_dipole() + # + # fh.write( + # "{:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f} {:>10.6f}\n".format( + # chargesAndDipole[0], chargesAndDipole[1], chargesAndDipole[2], chargesAndDipole[3], chargesAndDipole[4] + # ) + # ) diff --git a/diceplayer/shared/utils/logger.py b/diceplayer/shared/utils/logger.py index 30fa6ee..52430d9 100644 --- a/diceplayer/shared/utils/logger.py +++ b/diceplayer/shared/utils/logger.py @@ -1,27 +1,54 @@ import logging +def valid_logger(func): + def wrapper(*args, **kwargs): + logger = args[0] + assert logger._was_set, \ + "Logger is not set. Please call set_logger() first." + + return func(*args, **kwargs) + + return wrapper + + class Logger: outfile = None _logger = None - _instance = None - def __new__(cls, *args, **kwargs): - if not getattr(cls, '_instance'): - cls._instance = super(Logger, cls).__new__(cls) - return cls._instance + _was_set = False - def set_logger(self, logger_name, outfile='run.log', level=logging.INFO): + def __init__(self, logger_name): + if self._logger is None: + self._logger = logging.getLogger(logger_name) + + def set_logger(self, outfile='run.log', level=logging.INFO): self.outfile = outfile - self._logger = logging.getLogger(logger_name) - if level is not None: self._logger.setLevel(level) self._create_handlers() + self._was_set = True + + @valid_logger + def info(self, message): + self._logger.info(message) + + @valid_logger + def debug(self, message): + self._logger.debug(message) + + @valid_logger + def warning(self, message): + self._logger.warning(message) + + @valid_logger + def error(self, message): + self._logger.error(message) + def _create_handlers(self): handlers = [] if self.outfile is not None: diff --git a/tests/shared/utils/test_logger.py b/tests/shared/utils/test_logger.py index f97ec54..03d47b5 100644 --- a/tests/shared/utils/test_logger.py +++ b/tests/shared/utils/test_logger.py @@ -1,34 +1,96 @@ -from diceplayer.shared.utils.logger import Logger +from diceplayer.shared.utils.logger import Logger, valid_logger +import logging + +from unittest import mock import unittest +class TestValidateLogger(unittest.TestCase): + def test_validate_logger(self): + class MockLogger: + _was_set = True + + @valid_logger + def test_func(self): + pass + + MockLogger().test_func() + + def test_validate_logger_exception(self): + class MockLogger: + _was_set = False + + @valid_logger + def test_func(self): + pass + + with self.assertRaises(AssertionError): + MockLogger().test_func() + + class TestLogger(unittest.TestCase): def test_class_instantiation(self): - logger = Logger() + logger = Logger('test') self.assertIsInstance(logger, Logger) - def test_singleton(self): - logger1 = Logger() - logger2 = Logger() - - self.assertIs(logger1, logger2) - + @mock.patch('builtins.open', mock.mock_open()) def test_set_logger(self): - logger = Logger() - logger.set_logger('test_logger') + logger = Logger('test') + logger.set_logger() self.assertIsNotNone(logger._logger) - self.assertEqual(logger._logger.name, 'test_logger') + self.assertEqual(logger._logger.name, 'test') + @mock.patch('builtins.open', mock.mock_open()) def test_close(self): - logger = Logger() - logger.set_logger('test_logger') + logger = Logger('test') + logger.set_logger() logger.close() self.assertEqual(len(logger._logger.handlers), 0) + @mock.patch('builtins.open', mock.mock_open()) + def test_info(self): + logger = Logger('test') + logger.set_logger() + + with self.assertLogs(level='INFO') as cm: + logger.info('test') + + self.assertEqual(cm.output, ['INFO:test:test']) + + @mock.patch('builtins.open', mock.mock_open()) + def test_debug(self): + logger = Logger('test') + logger.set_logger(level=logging.DEBUG) + + with self.assertLogs(level='DEBUG') as cm: + logger.debug('test') + + self.assertEqual(cm.output, ['DEBUG:test:test']) + + @mock.patch('builtins.open', mock.mock_open()) + def test_warning(self): + logger = Logger('test') + logger.set_logger() + + with self.assertLogs(level='WARNING') as cm: + logger.warning('test') + + self.assertEqual(cm.output, ['WARNING:test:test']) + + @mock.patch('builtins.open', mock.mock_open()) + def test_error(self): + logger = Logger('test') + logger.set_logger() + + with self.assertLogs(level='ERROR') as cm: + logger.error('test') + + self.assertEqual(cm.output, ['ERROR:test:test']) + if __name__ == '__main__': unittest.main()