Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ between published tags; older pre-5.0 notes are retained below.

For current interfaces, see the [consumer guide](docs/consumer-guide.md).

## 5.88.1

- Prefer the current predictor DataFrame API for cache fallbacks, retaining
compatibility with predictors that only implement the legacy API (#432).
- Require GTFParse's released Polars deprecation fix and mhctools' predictor
output handle cleanup. Close a test fixture's input file explicitly.
- Assert expected scientific diagnostics in tests and fail the suite on any
unexpected warning.

## 5.88.0

- Import native Exacto peptide variants, primary structures and translations,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ignore = ["F821", "E501", "F841", "E731", "E741", "E722", "E721"]
"test*/*" = ["F401"]

[tool.pytest.ini_options]
filterwarnings = ["error::FutureWarning"]
filterwarnings = ["error"]
markers = [
"isovar: exercises the installed Isovar optional integration",
"pirlygenes: exercises the installed PirlyGenes optional integration",
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
numpy>=2.0.0,<3.0.0
pandas>=2.0.0
mhctools>=3.44.26
mhctools>=3.45.1
varcode>=4.18.0
gtfparse>=2.7.0,<3.0.0
gtfparse>=2.8.1,<3.0.0
mhcgnomes>=3.4.0
pyensembl>=2.3.0,<3.0.0
# PEP 440 versions and validated package names for release preflight
Expand Down
26 changes: 24 additions & 2 deletions tests/test_cached_predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,36 @@ def _stamp(self, df):
df["kind"] = "pMHC_affinity"
return df

def predict_peptides_dataframe(self, peptides):
return self._stamp(super().predict_peptides_dataframe(peptides))
def predict_dataframe(self, peptides, sample_name="", n_flanks=None, c_flanks=None):
return self._stamp(super().predict_dataframe(
peptides, sample_name=sample_name, n_flanks=n_flanks, c_flanks=c_flanks))

def predict_proteins_dataframe(self, name_to_seq):
return self._stamp(super().predict_proteins_dataframe(name_to_seq))


class TestFallback:
def test_legacy_only_fallback_still_fills_misses(self):
from types import SimpleNamespace

predictor = _matched_fallback(name="random", version="1.0")
fallback = SimpleNamespace(
predict_peptides_dataframe=predictor.predict_dataframe,
alleles=predictor.alleles,
)
cache = CachedPredictor.from_dataframe(_df([_row()]), fallback=fallback)
assert cache.predict_dataframe(["GILGFVFTL"]).peptide.tolist() == ["GILGFVFTL"]

def test_current_fallback_api_takes_precedence(self, monkeypatch):
fallback = _matched_fallback(name="random", version="1.0")

def deprecated(*args, **kwargs):
pytest.fail("Cache called the deprecated fallback API")

monkeypatch.setattr(fallback, "predict_peptides_dataframe", deprecated)
cache = CachedPredictor.from_dataframe(_df([_row()]), fallback=fallback)
assert cache.predict_dataframe(["GILGFVFTL"]).peptide.tolist() == ["GILGFVFTL"]

def test_fallback_fills_miss(self):
cache = CachedPredictor.from_dataframe(
_df([_row()]),
Expand Down
14 changes: 12 additions & 2 deletions tests/test_consumer_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2866,11 +2866,21 @@ def table(value, method, version):
sources = {"only": table(50., "original", "")}
else:
sources = {"only": table(50., "original", None).drop(columns="predictor_version")}
combined = combine_sources(sources, sample_name="p")
if case == "versioned":
with pytest.warns(UserWarning) as diagnostics:
combined = combine_sources(sources, sample_name="p")
assert [str(warning.message) for warning in diagnostics] == [
"Model 'original' has conflicting versions: '4.1b' vs '4.2'",
"Model 'original' has conflicting versions: '4.1b' vs ''",
]
with pytest.warns(UserWarning, match="Multiple predictor versions"):
wide = combined.to_wide()
else:
combined = combine_sources(sources, sample_name="p")
wide = combined.to_wide()
policy = dict(ascending=True, strata=["source_label"])
before = rank_candidates(combined, "affinity.value", **policy)
for suffix, writer, method, reader in DELIMITED_IO_TWINS:
wide = combined.to_wide()
path = tmp_path / f"combined.{suffix}"
if writer_style == "result":
method(wide, path)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_io_pvacseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@


def _data_row_count(path):
return sum(1 for _ in open(path)) - 1
with open(path) as handle:
return sum(1 for _ in handle) - 1


# ---------------------------------------------------------------------------
Expand Down
18 changes: 10 additions & 8 deletions tests/test_peptide_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,10 +513,11 @@ def test_default_methods_settles_the_dependence():
"""An explicit default names one model; that model's mode applies."""
df = _two_model_presentation_df()

scores = evaluate_scores(
df, peptide_view(Presentation.score), kind_support=_SPLIT_SUPPORT,
default_methods={"pMHC_presentation": "netmhcpan"},
)
with pytest.warns(UserWarning, match="not a joint multi-allele aggregate"):
scores = evaluate_scores(
df, peptide_view(Presentation.score), kind_support=_SPLIT_SUPPORT,
default_methods={"pMHC_presentation": "netmhcpan"},
)

# netmhcpan is single_allele: best of 0.9 / 0.4 across the peptide.
assert scores.tolist()[:2] == [0.9, 0.9]
Expand All @@ -526,10 +527,11 @@ def test_filter_auto_aggregation_binds_the_method_for_dependence():
"""apply_filter binds one method per iteration; the resolver must see it."""
df = _two_model_presentation_df()

kept = apply_filter(
df, parse("peptide_view(el.score) >= 0.5"),
kind_support=_SPLIT_SUPPORT,
)
with pytest.warns(UserWarning, match="not a joint multi-allele aggregate"):
kept = apply_filter(
df, parse("peptide_view(el.score) >= 0.5"),
kind_support=_SPLIT_SUPPORT,
)

assert len(kept) > 0

Expand Down
6 changes: 4 additions & 2 deletions tests/test_ranking.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,8 @@ def test_default_methods_processing_synonyms(key):
])
from topiary.ranking import Processing
ctx = EvalContext(df, default_methods={key: "mhcflurry"})
assert Processing.score.eval(ctx).iloc[0] == 0.75
with pytest.warns(UserWarning, match="peptide-level.*rows name alleles"):
assert Processing.score.eval(ctx).iloc[0] == 0.75


def test_default_methods_accepts_multiple_synonyms_in_one_dict():
Expand Down Expand Up @@ -3393,7 +3394,8 @@ def test_a_filter_on_an_unpopulated_reserved_scope_keeps_nothing():
from topiary import apply_filter

df = _make_df(_UNSCOPED_ROW)
assert len(apply_filter(df, parse("shuffled.affinity < 500"))) == 0
with pytest.warns(UserWarning, match="which this frame does not have"):
assert len(apply_filter(df, parse("shuffled.affinity < 500"))) == 0
# The same expression keeps the row once the producer supplies the column.
supplied = _make_df([{**_UNSCOPED_ROW[0], "shuffled_value": 400.0}])
assert len(apply_filter(supplied, parse("shuffled.affinity < 500"))) == 1
2 changes: 1 addition & 1 deletion topiary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
encode_amino_acids,
)

__version__ = "5.88.0"
__version__ = "5.88.1"

__all__ = [
"normalize_isovar_rna_support",
Expand Down
6 changes: 3 additions & 3 deletions topiary/cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,10 +877,10 @@ def _fallback_resolve(self, peptides) -> None:
f"no fallback set. Missed peptides: {missed_preview}{extra}."
)

if hasattr(self.fallback, "predict_peptides_dataframe"):
fb_df = self.fallback.predict_peptides_dataframe(peptides)
elif hasattr(self.fallback, "predict_dataframe"):
if hasattr(self.fallback, "predict_dataframe"):
fb_df = self.fallback.predict_dataframe(peptides)
elif hasattr(self.fallback, "predict_peptides_dataframe"):
fb_df = self.fallback.predict_peptides_dataframe(peptides)
else:
raise TypeError(
f"fallback does not implement predict_peptides_dataframe "
Expand Down
Loading