diff --git a/alphabase/peptide/precursor.py b/alphabase/peptide/precursor.py index 82dad36b..ce3684cf 100644 --- a/alphabase/peptide/precursor.py +++ b/alphabase/peptide/precursor.py @@ -15,6 +15,11 @@ from alphabase.peptide.mass_calc import calc_peptide_masses_for_same_len_seqs +def _xxh64_str_intdigest(text: str, *, seed: int) -> int: + """xxhash>=4 no longer encodes strings implicitly; utf-8 keeps hash values identical to xxhash<4.""" + return xxh64_intdigest(text.encode(), seed=seed) + + def refine_precursor_df( df: pd.DataFrame, drop_frag_idx=False, @@ -178,9 +183,9 @@ def get_mod_seq_hash( """ return np.array( [ - xxh64_intdigest(sequence, seed=seed), - xxh64_intdigest(mods, seed=seed), - xxh64_intdigest(mod_sites, seed=seed), + _xxh64_str_intdigest(sequence, seed=seed), + _xxh64_str_intdigest(mods, seed=seed), + _xxh64_str_intdigest(mod_sites, seed=seed), ], dtype=np.uint64, ).sum() # use np.sum to prevent overflow @@ -233,15 +238,15 @@ def get_mod_seq_charge_hash( def hash_mod_seq_df(precursor_df: pd.DataFrame, *, seed=0): """Internal function""" hash_vals = precursor_df.sequence.apply( - lambda x: xxh64_intdigest(x, seed=seed) + lambda x: _xxh64_str_intdigest(x, seed=seed) ).to_numpy(copy=True, dtype=np.uint64) hash_vals += ( - precursor_df.mods.apply(lambda x: xxh64_intdigest(x, seed=seed)) + precursor_df.mods.apply(lambda x: _xxh64_str_intdigest(x, seed=seed)) .astype(np.uint64) .values ) hash_vals += ( - precursor_df.mod_sites.apply(lambda x: xxh64_intdigest(x, seed=seed)) + precursor_df.mod_sites.apply(lambda x: _xxh64_str_intdigest(x, seed=seed)) .astype(np.uint64) .values ) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 4d5180fb..f61f73a2 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -2,22 +2,38 @@ import importlib.util import os +from importlib.metadata import version from pathlib import Path +import numpy as np import pandas as pd import pytest +from packaging.version import Version from alphabase.tools.data_downloader import DataShareDownloader TABLES_PACKAGE_UNAVAILABLE = importlib.util.find_spec("tables") is None NUMBA_UNAVAILABLE = importlib.util.find_spec("numba") is None +FIRST_NUMPY2_COMPATIBLE_TABLES_VERSION = Version("3.10") +FIRST_ABI_BREAKING_NUMPY_VERSION = Version("2.0") + +PYTABLES_NUMPY_ABI_BROKEN = not TABLES_PACKAGE_UNAVAILABLE and ( + Version(version("tables")) < FIRST_NUMPY2_COMPATIBLE_TABLES_VERSION + and Version(np.__version__) >= FIRST_ABI_BREAKING_NUMPY_VERSION +) + pytest.mark.optional_pytables_dependency = pytest.mark.skipif( TABLES_PACKAGE_UNAVAILABLE, reason="pytables package not installed. Install with `pip install alphabase[hdf]`", ) +pytest.mark.broken_pytables_numpy_abi = pytest.mark.skipif( + PYTABLES_NUMPY_ABI_BROKEN, + reason="tables<3.10 is built against numpy<2 and raises a dtype-size ValueError on import with numpy>=2", +) + pytest.mark.requires_numba = pytest.mark.skipif( NUMBA_UNAVAILABLE, reason="numba package not installed", diff --git a/tests/integration/test_pg_readers.py b/tests/integration/test_pg_readers.py index fdb8d989..ea6451e5 100644 --- a/tests/integration/test_pg_readers.py +++ b/tests/integration/test_pg_readers.py @@ -53,6 +53,7 @@ def test_import_csv_file_equivalent( pd.testing.assert_frame_equal(result_df, reference) + @pytest.mark.broken_pytables_numpy_abi @pytest.mark.optional_pytables_dependency def test_import_hdf_file_equivalent( self, example_alphapept_hdf: tuple[str, pd.DataFrame] @@ -108,6 +109,7 @@ def test_import_csv_file( PGCols.DECOY_INDICATOR, ] + @pytest.mark.broken_pytables_numpy_abi @pytest.mark.optional_pytables_dependency @pytest.mark.parametrize( ("measurement_regex", "expected_shape", "expected_colums"),