Skip to content

Commit 7e0a05b

Browse files
committed
Add __repr__ to Region and CacheMiss for better debugging (fixes #1219)
- Region.__repr__ returns a constructor-style string, e.g. Region('2L', 100000, 200000) so that Region objects display usefully inside lists, dicts, and REPL sessions, complementing the existing __str__ (e.g. '2L:100,000-200,000'). - CacheMiss gains a default message and __repr__: - CacheMiss() → str 'Cache miss: requested item not found in cache.' - CacheMiss('key') → str "Cache miss for key: 'key'" - repr(CacheMiss('k')) → "CacheMiss('k')" - Added tests/test_util.py covering both classes.
1 parent 4c31774 commit 7e0a05b

2 files changed

Lines changed: 97 additions & 1 deletion

File tree

malariagen_data/util.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,9 @@ def __eq__(self, other):
570570
and (self.end == other.end)
571571
)
572572

573+
def __repr__(self):
574+
return f"Region({self._contig!r}, {self._start!r}, {self._end!r})"
575+
573576
def __str__(self):
574577
out = self._contig
575578
if self._start is not None or self._end is not None:
@@ -904,7 +907,20 @@ def _jitter(a, fraction):
904907

905908

906909
class CacheMiss(Exception):
907-
pass
910+
"""Raised when a requested item is not present in the cache."""
911+
912+
def __init__(self, key=None):
913+
self.key = key
914+
if key is not None:
915+
message = f"Cache miss for key: {key!r}"
916+
else:
917+
message = "Cache miss: requested item not found in cache."
918+
super().__init__(message)
919+
920+
def __repr__(self):
921+
if self.key is not None:
922+
return f"CacheMiss({self.key!r})"
923+
return "CacheMiss()"
908924

909925

910926
class LoggingHelper:

tests/test_util.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Tests for Region.__repr__ and CacheMiss.__repr__ / default message."""
2+
import importlib.util as ilu
3+
import sys
4+
5+
# Load malariagen_data/util.py directly, bypassing the package __init__.py
6+
# which has heavy transitive imports (bokeh, plotly, etc.).
7+
_spec = ilu.spec_from_file_location(
8+
"malariagen_data.util",
9+
"malariagen_data/util.py",
10+
)
11+
_util = ilu.module_from_spec(_spec)
12+
sys.modules["malariagen_data.util"] = _util
13+
14+
try:
15+
_spec.loader.exec_module(_util)
16+
except Exception:
17+
# If util.py itself can't be loaded (missing deps), fall back to a
18+
# normal import so the tests still work in a fully-installed env.
19+
import malariagen_data.util as _util # type: ignore[assignment]
20+
21+
Region = _util.Region
22+
CacheMiss = _util.CacheMiss
23+
24+
import pytest # noqa: E402
25+
26+
27+
# ---------------------------------------------------------------------------
28+
# Region
29+
# ---------------------------------------------------------------------------
30+
31+
def test_region_repr_contig_only():
32+
r = Region("2L")
33+
assert repr(r) == "Region('2L', None, None)"
34+
assert str(r) == "2L"
35+
36+
37+
def test_region_repr_with_coords():
38+
r = Region("2L", 100_000, 200_000)
39+
assert repr(r) == "Region('2L', 100000, 200000)"
40+
assert str(r) == "2L:100,000-200,000"
41+
42+
43+
def test_region_repr_in_list():
44+
regions = [Region("2L", 10, 20), Region("3R", 30, 40)]
45+
assert repr(regions) == "[Region('2L', 10, 20), Region('3R', 30, 40)]"
46+
47+
48+
def test_region_repr_start_only():
49+
r = Region("X", start=500, end=None)
50+
assert repr(r) == "Region('X', 500, None)"
51+
assert str(r) == "X:500-"
52+
53+
54+
# ---------------------------------------------------------------------------
55+
# CacheMiss
56+
# ---------------------------------------------------------------------------
57+
58+
def test_cache_miss_no_key():
59+
cm = CacheMiss()
60+
assert repr(cm) == "CacheMiss()"
61+
assert "Cache miss" in str(cm)
62+
63+
64+
def test_cache_miss_string_key():
65+
cm = CacheMiss("my_key")
66+
assert repr(cm) == "CacheMiss('my_key')"
67+
assert "my_key" in str(cm)
68+
69+
70+
def test_cache_miss_tuple_key():
71+
cm = CacheMiss(("contig", 100))
72+
assert repr(cm) == "CacheMiss(('contig', 100))"
73+
assert "('contig', 100)" in str(cm)
74+
75+
76+
def test_cache_miss_is_exception():
77+
with pytest.raises(CacheMiss) as exc_info:
78+
raise CacheMiss("lookup_key")
79+
assert "lookup_key" in str(exc_info.value)
80+
assert repr(exc_info.value) == "CacheMiss('lookup_key')"

0 commit comments

Comments
 (0)