From 826957ce70f90e885fa7257cc598f64f09830aa3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:55:10 -0400 Subject: [PATCH] [python] `verify_core_versions()` during `import tiledbsoma` (#2437) (#2443) * verify_core_versions() during `import tiledbsoma` * add comment Co-authored-by: Ryan Williams --- apis/python/src/tiledbsoma/__init__.py | 6 ++ .../src/tiledbsoma/_general_utilities.py | 79 ++++++++++++++++--- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/apis/python/src/tiledbsoma/__init__.py b/apis/python/src/tiledbsoma/__init__.py index 03a67e72a7..b33263f1c2 100644 --- a/apis/python/src/tiledbsoma/__init__.py +++ b/apis/python/src/tiledbsoma/__init__.py @@ -156,6 +156,7 @@ get_SOMA_version, get_storage_engine, show_package_versions, + verify_core_versions, ) from ._indexer import IntIndexer, tiledbsoma_build_index from ._measurement import Measurement @@ -168,6 +169,11 @@ tiledbsoma_stats_reset, ) +# Ensure TileDB-Py and libtiledbsoma have matching core versions; if they don't, undefined behavior +# / segfaults may ensue. Once libtiledbsoma is the only "path to core" (cf. #1632), this can be +# removed. +verify_core_versions() + __version__ = get_implementation_version() __all__ = [ diff --git a/apis/python/src/tiledbsoma/_general_utilities.py b/apis/python/src/tiledbsoma/_general_utilities.py index ff5d8124e9..c37e851121 100644 --- a/apis/python/src/tiledbsoma/_general_utilities.py +++ b/apis/python/src/tiledbsoma/_general_utilities.py @@ -5,13 +5,14 @@ """General utility functions. """ - +import os import platform import sys +from re import fullmatch import tiledb -from .pytiledbsoma import version as libtiledbsoma_version +from .pytiledbsoma import version as libtiledbsoma_core_version_str def get_SOMA_version() -> str: @@ -59,19 +60,75 @@ def get_storage_engine() -> str: return "tiledb" +def get_tiledb_py_core_version() -> str: + """Returns the version of libtiledb ("core") used by the `tiledb` Python library. + + Lifecycle: maturing + """ + return ".".join(str(ijk) for ijk in list(tiledb.libtiledb.version())) + + +def get_libtiledbsoma_core_version() -> str: + """Returns the version of libtiledb ("core") used by libtiledbsoma. + + Lifecycle: maturing + """ + v = libtiledbsoma_core_version_str() + m = fullmatch(r"libtiledb=(\d+\.\d+\.\d+)", v) + if m is None: + raise ValueError(f"Unexpected libtiledbsoma_core_version: {v}") + return m.group(1) + + +# Set this env var to "err" to print an error to stderr when TileDB-Py's and libtiledbsoma's core +# versions mismatch (by default, an AssertionError is raised). +TILEDB_CORE_MISMATCHED_VERSIONS_ERROR_LEVEL_VAR = ( + "TILEDB_CORE_MISMATCHED_VERSIONS_ERROR_LEVEL" +) + + +def verify_core_versions() -> None: + """Verify that the versions of libtiledb used by the `tiledb` Python library and + libtiledbsoma are the same. + + See discussion on https://github.com/single-cell-data/TileDB-SOMA/issues/1837; this + will be unnecessary when libtiledbsoma is the only "path to core" (cf. + https://github.com/single-cell-data/TileDB-SOMA/issues/1632). + + Lifecycle: maturing + """ + tiledb_py_core_version = get_tiledb_py_core_version() + libtiledbsoma_core_version = get_libtiledbsoma_core_version() + if tiledb_py_core_version != libtiledbsoma_core_version: + msg = "libtiledb versions used by tiledb and libtiledbsoma differ: %s != %s" % ( + tiledb_py_core_version, + libtiledbsoma_core_version, + ) + if os.environ.get(TILEDB_CORE_MISMATCHED_VERSIONS_ERROR_LEVEL_VAR) == "err": + print(msg, file=sys.stderr) + print( + f"Continuing, since ${TILEDB_CORE_MISMATCHED_VERSIONS_ERROR_LEVEL_VAR} is set, but it is highly recommended you fix the core version mismatch, as undefined behavior and segfaults can result.", + file=sys.stderr, + ) + else: + raise AssertionError( + f"libtiledb versions used by tiledb and libtiledbsoma differ: {tiledb_py_core_version} != {libtiledbsoma_core_version}" + ) + + def show_package_versions() -> None: """Nominal use is for bug reports, so issue filers and issue fixers can be on the same page. Lifecycle: maturing """ - print("tiledbsoma.__version__ ", get_implementation_version()) - print("TileDB-Py tiledb.version() ", tiledb.version()) - print( - "TileDB core version ", - ".".join(str(ijk) for ijk in list(tiledb.libtiledb.version())), - ) - print("libtiledbsoma version() ", libtiledbsoma_version()) - print("python version ", ".".join(str(v) for v in sys.version_info)) u = platform.uname() - print("OS version ", u.system, u.release) + # fmt: off + print("tiledbsoma.__version__ ", get_implementation_version()) + print("TileDB-Py version ", ".".join(str(v) for v in tiledb.version())) + print("TileDB core version (tiledb) ", get_tiledb_py_core_version()) + print("TileDB core version (libtiledbsoma)", get_libtiledbsoma_core_version()) + print("python version ", ".".join(str(v) for v in sys.version_info)) + print("OS version ", u.system, u.release) + # fmt: on + verify_core_versions()