Skip to content

Commit 838c4ee

Browse files
fix: Cache name resolver improvements addressing Copilot PR review
- Use instance-level getattr for proper property descriptor resolution - Add type validation to ensure string returns - Update docstrings from 'should' to 'may' to reflect optional overrides - Add regression tests for MRO edge case handling
1 parent 8c9d457 commit 838c4ee

2 files changed

Lines changed: 78 additions & 10 deletions

File tree

malariagen_data/anopheles.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,26 +182,44 @@ def __init__(
182182
def _get_xpehh_gwss_cache_name(self):
183183
"""Safe resolver for xpehh gwss cache name.
184184
185-
Subclasses should define _xpehh_gwss_cache_name as a class attribute.
185+
Subclasses may define _xpehh_gwss_cache_name as a class attribute.
186186
This method safely retrieves it or returns a default if unavailable.
187187
"""
188-
cache_name = getattr(self.__class__, "_xpehh_gwss_cache_name", None)
189-
if cache_name is None:
188+
try:
189+
# Try to get the cache name from the instance (respects MRO)
190+
cache_name = getattr(self, "_xpehh_gwss_cache_name")
191+
# Validate it's actually a string, not a descriptor or other object
192+
if not isinstance(cache_name, str):
193+
raise TypeError(
194+
f"_xpehh_gwss_cache_name must resolve to a string, "
195+
f"got {type(cache_name).__name__}"
196+
)
197+
return cache_name
198+
except (AttributeError, TypeError):
190199
# Fallback to a generic cache name if subclass hasn't defined one
191-
cache_name = "xpehh_gwss_v1"
192-
return cache_name
200+
# or if the attribute isn't a proper string
201+
return "xpehh_gwss_v1"
193202

194203
def _get_ihs_gwss_cache_name(self):
195204
"""Safe resolver for ihs gwss cache name.
196205
197-
Subclasses should define _ihs_gwss_cache_name as a class attribute.
206+
Subclasses may define _ihs_gwss_cache_name as a class attribute.
198207
This method safely retrieves it or returns a default if unavailable.
199208
"""
200-
cache_name = getattr(self.__class__, "_ihs_gwss_cache_name", None)
201-
if cache_name is None:
209+
try:
210+
# Try to get the cache name from the instance (respects MRO)
211+
cache_name = getattr(self, "_ihs_gwss_cache_name")
212+
# Validate it's actually a string, not a descriptor or other object
213+
if not isinstance(cache_name, str):
214+
raise TypeError(
215+
f"_ihs_gwss_cache_name must resolve to a string, "
216+
f"got {type(cache_name).__name__}"
217+
)
218+
return cache_name
219+
except (AttributeError, TypeError):
202220
# Fallback to a generic cache name if subclass hasn't defined one
203-
cache_name = "ihs_gwss_v1"
204-
return cache_name
221+
# or if the attribute isn't a proper string
222+
return "ihs_gwss_v1"
205223

206224
@staticmethod
207225
def _make_gene_cnv_label(gene_id, gene_name, cnv_type):
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Test cache name resolver methods for MRO edge case handling."""
2+
3+
from malariagen_data.anopheles import AnophelesDataResource
4+
5+
6+
class DummyAnophelesNoOverrides(AnophelesDataResource):
7+
"""Dummy subclass that doesn't override cache name properties.
8+
9+
Tests the edge case where resolver methods must return defaults
10+
instead of raising NotImplementedError.
11+
"""
12+
13+
def __init__(self):
14+
# Minimal initialization - we only test the resolver methods
15+
pass
16+
17+
18+
def test_get_xpehh_gwss_cache_name_returns_default():
19+
"""Test that _get_xpehh_gwss_cache_name returns default when not overridden."""
20+
instance = DummyAnophelesNoOverrides()
21+
22+
# Should not raise NotImplementedError
23+
cache_name = instance._get_xpehh_gwss_cache_name()
24+
25+
# Should return default string
26+
assert isinstance(cache_name, str)
27+
assert cache_name == "xpehh_gwss_v1"
28+
29+
30+
def test_get_ihs_gwss_cache_name_returns_default():
31+
"""Test that _get_ihs_gwss_cache_name returns default when not overridden."""
32+
instance = DummyAnophelesNoOverrides()
33+
34+
# Should not raise NotImplementedError
35+
cache_name = instance._get_ihs_gwss_cache_name()
36+
37+
# Should return default string
38+
assert isinstance(cache_name, str)
39+
assert cache_name == "ihs_gwss_v1"
40+
41+
42+
def test_cache_name_resolvers_always_return_string():
43+
"""Test that both resolvers always return strings, never other types."""
44+
instance = DummyAnophelesNoOverrides()
45+
46+
xpehh_name = instance._get_xpehh_gwss_cache_name()
47+
ihs_name = instance._get_ihs_gwss_cache_name()
48+
49+
assert isinstance(xpehh_name, str), f"Expected str, got {type(xpehh_name).__name__}"
50+
assert isinstance(ihs_name, str), f"Expected str, got {type(ihs_name).__name__}"

0 commit comments

Comments
 (0)