From 2de6ac702dc0042c163c4e3a3ad5dbdb40338dac Mon Sep 17 00:00:00 2001 From: syedhamidali Date: Wed, 4 Sep 2024 01:25:15 -0400 Subject: [PATCH] FIX: Coverge Test 3 --- tests/io/test_auto_read.py | 71 ++++++++++++++------ xradar/io/auto_read.py | 131 ++++++++++++++++++++++++++++++------- 2 files changed, 160 insertions(+), 42 deletions(-) diff --git a/tests/io/test_auto_read.py b/tests/io/test_auto_read.py index 63e1b401..c4f98307 100644 --- a/tests/io/test_auto_read.py +++ b/tests/io/test_auto_read.py @@ -1,3 +1,4 @@ +import logging import signal from unittest.mock import MagicMock, patch @@ -10,6 +11,8 @@ # Mocked functions for testing def mock_open_success(file): mock_dtree = MagicMock(name="DataTree") + # Mock the behavior of georeference to return the same object without changing attrs + mock_dtree.xradar.georeference.return_value = mock_dtree return mock_dtree @@ -82,8 +85,8 @@ def test_read_nonlocal_dtree(sample_file): # Test for georeferencing and verbose output -def test_read_with_georeferencing_and_verbose(sample_file, capsys): - # Test successful reading with georeferencing and verbose mode. +def test_read_with_georeferencing_and_logging(sample_file, caplog): + # Test successful reading with georeferencing and log output. with ( patch("xradar.io.auto_read.io.__all__", ["open_nexradlevel2_datatree"]), patch( @@ -92,20 +95,21 @@ def test_read_with_georeferencing_and_verbose(sample_file, capsys): ), patch("xarray.core.dataset.Dataset.pipe", side_effect=mock_georeference), ): - dtree = auto_read.read(sample_file, georeference=True, verbose=True) - assert dtree is not None + with caplog.at_level(logging.DEBUG): + dtree = auto_read.read(sample_file, georeference=True, verbose=True) + assert dtree is not None - # Capture the printed output - captured = capsys.readouterr() - assert "Georeferencing radar data..." in captured.out - assert ( - "File opened successfully using open_nexradlevel2_datatree." in captured.out - ) + # Check that the log messages contain the correct information + assert "Georeferencing radar data..." in caplog.text + assert ( + "File opened successfully using open_nexradlevel2_datatree." + in caplog.text + ) # Test for exception handling and verbose output during failure -def test_read_failure_with_verbose_output(sample_file, capsys): - # Test that it handles exceptions and prints the verbose failure message. +def test_read_failure_with_verbose_output(sample_file, caplog): + # Test that it handles exceptions and logs the verbose failure message. with ( patch("xradar.io.auto_read.io.__all__", ["open_nexradlevel2_datatree"]), patch( @@ -113,15 +117,17 @@ def test_read_failure_with_verbose_output(sample_file, capsys): side_effect=mock_open_failure, ), ): - with pytest.raises( - ValueError, - match="File could not be opened by any supported format in xradar.io.", - ): - auto_read.read(sample_file, georeference=True, verbose=True) + with caplog.at_level(logging.DEBUG): + with pytest.raises( + ValueError, + match="File could not be opened by any supported format in xradar.io.", + ): + auto_read.read(sample_file, georeference=True, verbose=True) - # Capture the printed output - captured = capsys.readouterr() - assert "Failed to open with open_nexradlevel2_datatree" in captured.out + # Check that the failure log messages contain the correct information + assert ( + "Failed to open with open_nexradlevel2_datatree" in caplog.text + ) # Capturing log instead of print # Test for raising ValueError when no format can open the file @@ -195,3 +201,28 @@ def test_read_with_timeout(sample_file): auto_read.TimeoutException, match="Radar file reading timed out." ): auto_read.read(sample_file, timeout=1) + + +def test_read_comment_update(sample_file): + with ( + patch("xradar.io.auto_read.io.__all__", ["open_nexradlevel2_datatree"]), + patch( + "xradar.io.auto_read.io.open_nexradlevel2_datatree", + side_effect=mock_open_success, + ), + ): + dtree = auto_read.read(sample_file) + assert dtree is not None + + # Print the actual value of the 'comment' attribute for debugging + print(f"Actual comment: {dtree.attrs['comment']}") + + # The initial comment is "im/exported using xradar" (lowercase 'i') + expected_comment_start = "im/exported using xradar" + + # Ensure the comment starts with the correct initial value + assert dtree.attrs["comment"].startswith(expected_comment_start) + + # Ensure the comment has the correct format with 'nexradlevel2' + expected_comment_end = ",\n'nexradlevel2'" + assert dtree.attrs["comment"].endswith(expected_comment_end) diff --git a/xradar/io/auto_read.py b/xradar/io/auto_read.py index d4f0335d..9f27b258 100644 --- a/xradar/io/auto_read.py +++ b/xradar/io/auto_read.py @@ -6,11 +6,15 @@ XRadar Auto Reader ================== +This module provides the ability to automatically read radar files using all +available formats in the `xradar.io` module. It supports handling various file +types, georeferencing, and logging, as well as a timeout mechanism for file reading. + .. autosummary:: :nosignatures: :toctree: generated/ - {} + read """ __all__ = [ @@ -19,22 +23,69 @@ __doc__ = __doc__.format("\n ".join(__all__)) -from .. import io # noqa +import logging import signal +from .. import io # noqa + +# Setup a logger for this module +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.WARNING +) # Default log level to suppress debug logs unless verbose is set + -# Custom exception for handling timeouts class TimeoutException(Exception): + """ + Custom exception to handle file read timeouts. + + This exception is raised when the radar file reading process exceeds the + specified timeout duration. + """ + pass -# Timeout handler def timeout_handler(signum, frame): + """ + Timeout handler to raise a TimeoutException. + + This function is triggered by the alarm signal when the radar file reading + exceeds the allowed timeout duration. + + Parameters + ---------- + signum : int + The signal number (in this case, SIGALRM). + frame : frame object + The current stack frame at the point where the signal was received. + + Raises + ------ + TimeoutException + If the reading operation takes too long and exceeds the timeout. + """ raise TimeoutException("Radar file reading timed out.") -# Decorator for handling timeouts def with_timeout(timeout): + """ + Decorator to enforce a timeout on the file reading process. + + This decorator wraps the file reading function to ensure that if it takes longer than the + specified `timeout` duration, it raises a `TimeoutException`. + + Parameters + ---------- + timeout : int + The maximum number of seconds allowed for the file reading process. + + Returns + ------- + function + A wrapped function that raises `TimeoutException` if it exceeds the timeout. + """ + def decorator(func): def wrapper(*args, **kwargs): # Set the signal handler and a timeout @@ -56,8 +107,9 @@ def read(file, georeference=True, verbose=False, timeout=None): Attempt to read a radar file using all available formats in xradar.io. This function iterates over all the available file-opening functions in the - xradar.io module and attempts to open the provided radar file. If successful, - it can optionally georeference the data (adding x, y, z coordinates). + `xradar.io` module and attempts to open the provided radar file. If successful, + it can optionally georeference the data (adding x, y, z coordinates) and log + detailed processing information. Parameters ---------- @@ -66,19 +118,20 @@ def read(file, georeference=True, verbose=False, timeout=None): georeference : bool, optional If True, georeference the radar data by adding x, y, z coordinates (default is True). verbose : bool, optional - If True, prints out detailed processing information (default is False). + If True, prints out detailed processing information (default is False). When set to True, + debug-level logs are enabled. When False, only warnings and errors are logged. timeout : int or None, optional Timeout in seconds for reading the radar file. If None, no timeout is applied (default is None). Returns ------- dtree : DataTree - A DataTree object containing the radar data. + A `DataTree` object containing the radar data. Raises ------ ValueError - If the file could not be opened by any supported format in xradar.io. + If the file could not be opened by any supported format in `xradar.io`. TimeoutException If reading the file takes longer than the specified timeout. @@ -93,7 +146,18 @@ def read(file, georeference=True, verbose=False, timeout=None): ----- This function relies on the `xradar` library to support various radar file formats. It tries to open the file using all available `open_` functions in the `xradar.io` module. + If a `comment` attribute exists in the radar file's metadata, this function appends the + radar type to the comment. The default comment is set to "im/exported using xradar". """ + # Configure logger level based on 'verbose' + if verbose: + logger.setLevel(logging.DEBUG) # Enable debug messages when verbose is True + logger.debug("Verbose mode activated.") + else: + logger.setLevel( + logging.WARNING + ) # Suppress debug messages when verbose is False + dtree = None # Wrap the read process with a timeout if specified @@ -107,16 +171,30 @@ def attempt_read(file): open_func = getattr(io, key) try: dtree = open_func(file) + + # Ensure the 'comment' key exists; if not, create it + if "comment" not in dtree.attrs: + logger.debug("Creating new 'comment' key.") + dtree.attrs["comment"] = "im/exported using xradar" + else: + logger.debug( + f"Existing 'comment': {dtree.attrs['comment']}" + ) + + # Append the key information to the comment without quotes + dtree.attrs["comment"] += f",\n{key.split('_')[1]}" + + # Log the updated comment + logger.debug(f"After update: {dtree.attrs['comment']}") + if georeference: - if verbose: - print("Georeferencing radar data...") + logger.debug("Georeferencing radar data...") dtree = dtree.xradar.georeference() - if verbose: - print(f"File opened successfully using {key}.") + + logger.debug(f"File opened successfully using {key}.") break except Exception as e: - if verbose: - print(f"Failed to open with {key}: {e}") + logger.debug(f"Failed to open with {key}: {e}") continue if dtree is None: @@ -133,16 +211,25 @@ def attempt_read(file): open_func = getattr(io, key) try: dtree = open_func(file) + + if "comment" not in dtree.attrs: + logger.debug("Creating new 'comment' key.") + dtree.attrs["comment"] = "im/exported using xradar" + else: + logger.debug( + f"Existing 'comment' before update: {dtree.attrs['comment']}" + ) + + dtree.attrs["comment"] += f",\n{key.split('_')[1]}" + if georeference: - if verbose: - print("Georeferencing radar data...") + logger.debug("Georeferencing radar data...") dtree = dtree.xradar.georeference() - if verbose: - print(f"File opened successfully using {key}.") + + logger.debug(f"File opened successfully using {key}.") break except Exception as e: - if verbose: - print(f"Failed to open with {key}: {e}") + logger.debug(f"Failed to open with {key}: {e}") continue if dtree is None: