From 04dd340a7c8b72b0bf9cca407ca57b19857e83e2 Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:36:18 +0100 Subject: [PATCH 1/2] Improve error message generated when invalid params are passed to hictkpy.File() --- src/file.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/file.cpp b/src/file.cpp index f5e618c..c1f4748 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -43,9 +43,34 @@ namespace hictkpy::file { static void ctor(hictk::File *fp, const std::filesystem::path &path, std::optional resolution, std::string_view matrix_type, std::string_view matrix_unit) { - new (fp) hictk::File{path.string(), static_cast(resolution.value_or(0)), - hictk::hic::ParseMatrixTypeStr(std::string{matrix_type}), - hictk::hic::ParseUnitStr(std::string{matrix_unit})}; + const auto resolution_ = static_cast(resolution.value_or(0)); + try { + new (fp) hictk::File{path.string(), resolution_, + hictk::hic::ParseMatrixTypeStr(std::string{matrix_type}), + hictk::hic::ParseUnitStr(std::string{matrix_unit})}; + // TODO all the exceptions should ideally be handled on the hictk side + // but this will have to do until the next release of hictk + } catch (const HighFive::Exception &e) { + std::string_view msg{e.what()}; + if (msg.find("Unable to open the group \"/resolutions/0\"") != std::string_view::npos) { + throw std::runtime_error( + "resolution is required and cannot be None when opening .mcool files"); + } + throw; + } catch (const std::runtime_error &e) { + std::string_view msg{e.what()}; + if (msg.find("resolution cannot be 0 when opening .hic files") != std::string_view::npos) { + throw std::runtime_error("resolution is required and cannot be None when opening .hic files"); + } + throw; + } + + if (resolution.has_value() && fp->resolution() != resolution_) { + // TODO this should also be handled by hictk + throw std::runtime_error( + fmt::format(FMT_STRING("resolution mismatch for file \"{}\": expected {}, found {}"), + fp->uri(), resolution_, fp->resolution())); + } } static std::string repr(const hictk::File &f) { From 9e8dcf6f50e35de963e971edb48c948696d503a7 Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Sat, 9 Nov 2024 19:26:25 +0100 Subject: [PATCH 2/2] Add tests --- test/test_file_ctors.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/test_file_ctors.py diff --git a/test/test_file_ctors.py b/test/test_file_ctors.py new file mode 100644 index 0000000..bc6d0c4 --- /dev/null +++ b/test/test_file_ctors.py @@ -0,0 +1,42 @@ +# Copyright (C) 2024 Roberto Rossini +# +# SPDX-License-Identifier: MIT + +import pathlib + +import pytest + +import hictkpy + +testdir = pathlib.Path(__file__).resolve().parent + +pytestmark = pytest.mark.parametrize( + "file,resolution", + [ + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), + ], +) + + +class TestClass: + def test_resolution_mismatch(self, file, resolution): + f = hictkpy.MultiResFile(file) + + if not f[resolution].is_cooler(): + pytest.skip(f'File "{file}" is not in .mcool format') + + assert 1_000_000 in f.resolutions() + with pytest.raises(RuntimeError, match="resolution mismatch"): + hictkpy.File(f"{file}::/resolutions/{resolution}", 1_000_000) + + def test_missing_resolution_param(self, file, resolution): + f = hictkpy.MultiResFile(file) + + if f[resolution].is_cooler(): + ext = ".mcool" + else: + ext = ".hic" + + with pytest.raises(RuntimeError, match=f"resolution is required and cannot be None when opening {ext} files"): + hictkpy.File(file)