diff --git a/.github/workflows/python-cqa.yml b/.github/workflows/python-cqa.yml index 7a13245d..741bfd78 100644 --- a/.github/workflows/python-cqa.yml +++ b/.github/workflows/python-cqa.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.11", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"] env: SEQREPO_ROOT_DIR: ./tests/data/seqrepo/latest steps: diff --git a/pyproject.toml b/pyproject.toml index f9e76d3f..afd6613c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: Scientific/Engineering :: Bio-Informatics", "Topic :: Scientific/Engineering :: Medical Science Apps.", ] @@ -33,7 +34,7 @@ keywords = [ "spdi", "variation" ] -requires-python = ">=3.11,<3.15" +requires-python = ">=3.11" dynamic = ["version"] dependencies = [ "pydantic~=2.1", @@ -49,7 +50,6 @@ extras = [ "biocommons.seqrepo>=0.5.1", "bioutils>=0.5.2", "hgvs>=2.0.0a0,<3.0", - "dill~=0.3.7", "click", "pysam>=0.24.0", ] diff --git a/src/ga4gh/vrs/extras/object_store.py b/src/ga4gh/vrs/extras/object_store.py index 8712bb31..576da868 100644 --- a/src/ga4gh/vrs/extras/object_store.py +++ b/src/ga4gh/vrs/extras/object_store.py @@ -1,10 +1,9 @@ +import pickle import sqlite3 from collections.abc import MutableMapping from threading import Lock from typing import Any -import dill - class Sqlite3MutableMapping(MutableMapping): """Class that can be used like a Python dictionary but that uses a sqlite3 database @@ -59,7 +58,7 @@ def __delitem__(self, key: Any) -> None: def __setitem__(self, key: Any, value: Any) -> None: cur = self.db.cursor() try: - ser = dill.dumps(value) + ser = pickle.dumps(value) cur.execute( "insert or replace into mapping(key, value) values (?, ?)", (key, sqlite3.Binary(ser)), @@ -75,7 +74,7 @@ def __getitem__(self, key: Any) -> Any: rows = cur.execute("select value from mapping where key = ?", (key,)) row0 = next(rows) if row0: - return dill.loads(row0[0]) # noqa: S301 + return pickle.loads(row0[0]) # noqa: S301 except StopIteration as e: raise KeyError("Key not found: " + str(key)) from e finally: diff --git a/tests/extras/test_object_store.py b/tests/extras/test_object_store.py index 4a11e27a..2d65910b 100644 --- a/tests/extras/test_object_store.py +++ b/tests/extras/test_object_store.py @@ -55,23 +55,31 @@ def test_complex(tmp_path): object_store.close() -def test_classes(tmp_path): - db_path = str(tmp_path) + "/test_complex.sqlite3" +class StoredObject: + """Custom class for object store round-trip tests. + + Defined at module level because pickle stores classes by reference, and a class + local to the test function can't be imported when unpickling. + """ + + def __init__(self, id_value): + """Initialize with an id and some nested attribute values.""" + self.id = id_value + self.A = "A" + self.B = ["B1", "B2", 3] + self.C = {"C1": "C1-value"} - class TestClass: - def __init__(self, id_value): - self.id = id_value - self.A = "A" - self.B = ["B1", "B2", 3] - self.C = {"C1": "C1-value"} + def somefunction(self, arg): + return f"{self.id}-{arg}" - def somefunction(self, arg): - return f"{self.id}-{arg}" + +def test_classes(tmp_path): + db_path = str(tmp_path) + "/test_complex.sqlite3" object_store = Sqlite3MutableMapping(db_path) - val1 = TestClass("val1") - val2 = TestClass("val2") + val1 = StoredObject("val1") + val2 = StoredObject("val2") object_store["val1"] = val1 object_store["val2"] = val2