Skip to content

Fix AttributeError when AMRFinderPlus subclass is missing from CARD conversion table - #34

Closed
sanjaynagi-eit wants to merge 1 commit into
AMRverse:mainfrom
sanjaynagi-eit:fix/issue-28-amrfp-subclass-attributeerror
Closed

Fix AttributeError when AMRFinderPlus subclass is missing from CARD conversion table#34
sanjaynagi-eit wants to merge 1 commit into
AMRverse:mainfrom
sanjaynagi-eit:fix/issue-28-amrfp-subclass-attributeerror

Conversation

@sanjaynagi-eit

Copy link
Copy Markdown

Fixes #28

The bug

Genotype._assign_drug_from_amrfp() in src/amrrules/genotype_parser.py looked up the AMRFinderPlus subclass in card_amrfp_conversion and immediately chained .get('drug', '-') / .get('class', '-') onto the result:

self.drug       = card_amrfp_conversion.get(self.amrfp_subclass).get('drug', '-')
self.drug_class = card_amrfp_conversion.get(self.amrfp_subclass).get('class', '-')

card_amrfp_conversion is built from the bundled, hand-maintained amrfp_to_card_drugs_classes.txt resource, which will always lag behind whatever AMRFinderPlus/NCBI database version is actually in use. When self.amrfp_subclass isn't a key in that table, the outer .get() returns None, and the chained .get('drug', '-') call raises:

AttributeError: 'NoneType' object has no attribute 'get'

This crashes the entire run instead of falling through to the "unassigned markers" fallback path that already exists a few lines below (if self.drug_class == '-': self.drug_class = 'unassigned markers'), which is clearly the designed-for behaviour for "we don't know the drug class".

As reported in #28, this is hit by FUSIDIC_ACID (underscore) vs FUSIDIC ACID (space) in the AMRFinderPlus subclass for fusA point mutations. We independently hit the same crash in a production pipeline: 19 failed runs, all on S. aureus samples where AMRFinderPlus reported an fusA subclass not present in the bundled conversion table.

The fix

Default the lookup to an empty dict when the subclass isn't found, so drug/drug_class fall back to '-' (and then to 'unassigned markers' via the existing logic) instead of crashing:

def _assign_drug_from_amrfp(self, card_amrfp_conversion):
    conversion = card_amrfp_conversion.get(self.amrfp_subclass)
    if conversion is None:
        warnings.warn(f"AMRFinderPlus subclass '{self.amrfp_subclass}' was not found in the AMRFP-to-CARD "
                       f"conversion table. Falling back to 'unassigned markers' for this marker.")
        conversion = {}
    self.drug = conversion.get('drug', '-')
    self.drug_class = conversion.get('class', '-')

This mirrors the .get(..., '-') fallback pattern already used in the neighbouring _assign_drug_from_rule() method, and uses warnings.warn() (the logging mechanism already used elsewhere in the codebase, e.g. src/amrrules/utils.py) so a gap in the conversion table is surfaced instead of silently swallowed.

Testing

I didn't find an existing pytest-style unit test suite for genotype_parser.py — the repo's tests/ directory is data-driven, comparing full CLI output against example files (docs/source/tests.rst), which felt too heavyweight to extend correctly for this narrow change without risking an unrelated/incorrect fixture update. Manually verified with a minimal reproduction of the reported traceback (an unmapped subclass, e.g. FUSIDIC_ACID) that the code now falls through to drug_class = 'unassigned markers' instead of raising.

Happy to add a regression test if you can point me to the preferred harness for this module.

…ion table

Fixes AMRverse#28

_assign_drug_from_amrfp() called card_amrfp_conversion.get(self.amrfp_subclass)
and then immediately chained .get('drug', '-') / .get('class', '-') onto the
result. When the AMRFinderPlus subclass has no entry in the hand-maintained
amrfp_to_card_drugs_classes.txt table, the outer .get() returns None and the
chained .get() raises AttributeError: 'NoneType' object has no attribute 'get',
crashing the whole run instead of falling through to the existing
"unassigned markers" fallback path a few lines below.

This now defaults the lookup to an empty dict when the subclass is missing
(matching the .get('drug class', '-') fallback pattern already used in
_assign_drug_from_rule), and emits a warnings.warn() so a gap in the
conversion table is visible instead of silent.
@sanjaynagi-eit

Copy link
Copy Markdown
Author

Re-opened as #35 from a different account. No code changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AttributeError in _assign_drug_from_amrfp

1 participant