Skip to content

Commit 1d3b64e

Browse files
Refactor PafAnnotation class: Update mass and composition calculations to use new parsing methods, add dict_composition method, and improve error handling for charge in annotations.
1 parent 607e101 commit 1d3b64e

4 files changed

Lines changed: 43 additions & 42 deletions

File tree

src/paftacular/parser.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ def mass(self, monoisotopic: bool = True, calculate_sequence: bool = False) -> f
8181
# Additional mass calculations based on sequence can be added here
8282
import peptacular as pt
8383

84-
sequence_mass = pt.mass(self.sequence, monoisotopic=monoisotopic, ion_type="n")
84+
annot = pt.parse(self.sequence)
85+
86+
if annot.has_charge:
87+
raise ValueError("Sequence in annotation should not have charge for mass calculation")
88+
89+
sequence_mass = annot.mass(monoisotopic=monoisotopic, ion_type="n")
8590
base_mass += sequence_mass
8691

8792
return base_mass
@@ -113,13 +118,23 @@ def composition(self, calculate_sequence: bool = False) -> Counter[ElementInfo]:
113118

114119
if calculate_sequence is True and self.sequence is not None:
115120
# Additional composition calculations based on sequence can be added here
116-
import petacualr as pt
121+
import peptacular as pt
117122

118-
seq_comp = pt.comp(self.sequence)
123+
annot = pt.parse(self.sequence)
124+
125+
if annot.has_charge:
126+
raise ValueError("Sequence in annotation should not have charge for mass calculation")
127+
128+
seq_comp = annot.comp()
119129
comp.update(seq_comp)
120130

121131
return comp
122132

133+
def dict_composition(self, calculate_sequence: bool = False) -> dict[str, int]:
134+
"""Get the elemental composition as a dictionary of element symbols to counts"""
135+
comp_counter = self.composition(calculate_sequence=calculate_sequence)
136+
return {str(elem): count for elem, count in comp_counter.items()}
137+
123138
@property
124139
def sequence(self) -> str | None:
125140
"""Get the peptide sequence if applicable, else None"""

tests/test_annot.py

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
from tacular import ELEMENT_LOOKUP
55

6-
from paftacular import PafAnnotation, mzPAFParser
6+
from paftacular import PafAnnotation, parse, parse_single
77
from paftacular.comps import (
88
Adduct,
99
ChemicalFormula,
@@ -271,8 +271,7 @@ def test_basic_mass(self):
271271
"""Test basic mass calculation"""
272272
annotation = PafAnnotation(ion_type=PeptideIon(series=IonSeries.B, position=2))
273273
# Should have some positive mass
274-
assert annotation.monoisotopic_mass > 0
275-
assert annotation.average_mass > 0
274+
assert annotation.mass() > 0
276275

277276
def test_mass_with_neutral_loss(self):
278277
"""Test mass with neutral loss"""
@@ -282,7 +281,7 @@ def test_mass_with_neutral_loss(self):
282281
neutral_losses=(NeutralLoss(count=-1, base_formula="H2O"),),
283282
)
284283
# Mass should be reduced by water
285-
assert with_loss.monoisotopic_mass < base.monoisotopic_mass
284+
assert with_loss.mass() < base.mass()
286285

287286
def test_mass_with_charge(self):
288287
"""Test mass calculation includes protonation"""
@@ -291,7 +290,7 @@ def test_mass_with_charge(self):
291290
charge=2,
292291
)
293292
# Should add 2 protons
294-
assert annotation.monoisotopic_mass > 0
293+
assert annotation.mass() > 0
295294

296295

297296
class TestComposition:
@@ -300,7 +299,7 @@ class TestComposition:
300299
def test_basic_composition(self):
301300
"""Test basic composition calculation"""
302301
annotation = PafAnnotation(ion_type=ChemicalFormula(formula="C6H12O6"))
303-
comp = annotation.composition
302+
comp = annotation.composition()
304303
assert isinstance(comp, Counter)
305304
# Should contain C, H, O elements
306305
assert any(elem.symbol == "C" for elem in comp)
@@ -313,7 +312,7 @@ def test_composition_with_charge(self):
313312
ion_type=ChemicalFormula(formula="C6H12O6"),
314313
charge=2,
315314
)
316-
comp = annotation.composition
315+
comp = annotation.composition()
317316
# Should have protons added
318317
h_element = ELEMENT_LOOKUP["H"]
319318
assert h_element in comp
@@ -324,7 +323,7 @@ def test_composition_with_neutral_loss(self):
324323
ion_type=ChemicalFormula(formula="C6H12O6"),
325324
neutral_losses=(NeutralLoss(count=-1, base_formula="H2O"),),
326325
)
327-
comp = annotation.composition
326+
comp = annotation.composition()
328327
# Composition should be affected by loss
329328
assert isinstance(comp, Counter)
330329

@@ -334,43 +333,37 @@ class TestParsing:
334333

335334
def test_parse_simple(self):
336335
"""Test parsing simple annotation"""
337-
parser = mzPAFParser()
338-
annotation = parser.parse_single("b2")
336+
annotation = parse_single("b2")
339337
assert annotation.ion_type.series == IonSeries.B
340338
assert annotation.ion_type.position == 2
341339

342340
def test_parse_with_sequence(self):
343341
"""Test parsing with sequence"""
344-
parser = mzPAFParser()
345-
annotation = parser.parse_single("y3{PEP}")
342+
annotation = parse_single("y3{PEP}")
346343
assert annotation.ion_type.series == IonSeries.Y
347344
assert annotation.ion_type.position == 3
348345
assert annotation.ion_type.sequence == "PEP"
349346

350347
def test_parse_with_neutral_loss(self):
351348
"""Test parsing with neutral loss"""
352-
parser = mzPAFParser()
353-
annotation = parser.parse_single("b2-H2O")
349+
annotation = parse_single("b2-H2O")
354350
assert len(annotation.neutral_losses) == 1
355351
assert annotation.neutral_losses[0].count == -1
356352

357353
def test_parse_with_adduct(self):
358354
"""Test parsing with adduct"""
359-
parser = mzPAFParser()
360-
annotation = parser.parse_single("b2[M+Na]")
355+
annotation = parse_single("b2[M+Na]")
361356
assert len(annotation.adducts) == 1
362357
assert annotation.adducts[0].count == 1
363358

364359
def test_parse_with_charge(self):
365360
"""Test parsing with charge"""
366-
parser = mzPAFParser()
367-
annotation = parser.parse_single("b2^2")
361+
annotation = parse_single("b2^2")
368362
assert annotation.charge == 2
369363

370364
def test_parse_multiple_annotations(self):
371365
"""Test parsing comma-separated annotations"""
372-
parser = mzPAFParser()
373-
annotations = parser.parse("b2, y3, a1")
366+
annotations = parse("b2, y3, a1")
374367
assert len(annotations) == 3
375368
assert annotations[0].ion_type.series == IonSeries.B
376369
assert annotations[1].ion_type.series == IonSeries.Y
@@ -389,18 +382,16 @@ def test_parse_roundtrip(self):
389382
"r[TMT126]",
390383
"f{C6H12O6}",
391384
]
392-
parser = mzPAFParser()
393385
for test_str in test_strings:
394-
annotation = parser.parse_single(test_str)
386+
annotation = parse_single(test_str)
395387
serialized = annotation.serialize()
396-
re_parsed = parser.parse_single(serialized)
388+
re_parsed = parse_single(serialized)
397389
assert annotation.ion_type == re_parsed.ion_type
398390

399391
def test_invalid_annotation(self):
400392
"""Test that invalid annotations raise errors"""
401-
parser = mzPAFParser()
402393
with pytest.raises(ValueError, match="Invalid mzPAF annotation"):
403-
parser.parse_single("invalid_annotation!")
394+
parse_single("invalid_annotation!")
404395

405396

406397
class TestFormulaProperties:
@@ -409,14 +400,14 @@ class TestFormulaProperties:
409400
def test_formula_property(self):
410401
"""Test formula property returns string"""
411402
annotation = PafAnnotation(ion_type=ChemicalFormula(formula="C6H12O6"))
412-
formula = annotation.formula
403+
formula = annotation.formula()
413404
assert isinstance(formula, str)
414405
assert len(formula) > 0
415406

416407
def test_proforma_formula_property(self):
417408
"""Test ProForma formula property"""
418409
annotation = PafAnnotation(ion_type=ChemicalFormula(formula="C6H12O6"))
419-
formula = annotation.proforma_formula
410+
formula = annotation.proforma_formula()
420411
assert isinstance(formula, str)
421412
assert len(formula) > 0
422413

tests/test_parser_types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from tacular import ELEMENT_LOOKUP
33

4-
from paftacular import PafAnnotation, mzPAFParser
4+
from paftacular import PafAnnotation, parse, parse_single
55
from paftacular.comps import (
66
ChemicalFormula,
77
ImmoniumIon,
@@ -17,7 +17,7 @@
1717

1818

1919
def parse_one(s: str) -> PafAnnotation:
20-
return mzPAFParser().parse_single(s)
20+
return parse_single(s)
2121

2222

2323
# ============================================================================
@@ -755,7 +755,7 @@ def test_complex_annotation_neutral_loss_isotope():
755755

756756

757757
def test_multiple_annotations_parse():
758-
anns = mzPAFParser().parse("y3, b2, p, IY")
758+
anns = parse("y3, b2, p, IY")
759759
assert len(anns) == 4
760760

761761
assert isinstance(anns[0].ion_type, PeptideIon)
@@ -771,7 +771,7 @@ def test_multiple_annotations_parse():
771771

772772

773773
def test_multiple_annotations_complex():
774-
anns = mzPAFParser().parse("y5-H2O, b3+NH3, p^2")
774+
anns = parse("y5-H2O, b3+NH3, p^2")
775775
assert len(anns) == 3
776776

777777
# First annotation with neutral loss

tmp.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import paftacular as pft
22

3-
print(pft.parse("y12{PEPTIDE}/3.4ppm*0.85")[0].mass(calculate_sequence=True))
3+
print(pft.parse("y12{PEPTIDE-[Oxidation]}/3.4ppm*0.85")[0].mass(calculate_sequence=True))
4+
print(pft.parse("y12{PEPTIDE-[Oxidation]}/3.4ppm*0.85")[0].dict_composition(calculate_sequence=True))
5+
print(pft.parse("y12{P[+10]EPTIDE-[Oxidation]}/3.4ppm*0.85")[0].mass(calculate_sequence=True))
46
print(pft.parse("p-2[iTRAQ115]"))
57
print(pft.parse_single("p-2[iTRAQ115]+2[iTRAQ115][M-2H2O+Na]^2").as_dict())
68
print(pft.parse_single("p-2[iTRAQ115]+2[iTRAQ115][M-2H2O+Na]^2").ion_type)
7-
"""
8-
{'ion_type': {}, 'analyte_reference': None, 'is_auxiliary': False, 'neutral_losses':
9-
[{'sign': -1, 'count': 2, '_formula': None, '_mass': None, '_reference': 'iTRAQ115'},
10-
{'sign': 1, 'count': 2, '_formula': None, '_mass': None, '_reference': 'iTRAQ115'}],
11-
'isotopes': [], 'adducts': [{'sign': -1, 'count': 2, '_formula': 'H2O'}, {'sign': 1, 'count': 1, '_formula': 'Na'}],
12-
'charge': 2, 'mass_error': None, 'confidence': None}
13-
"""

0 commit comments

Comments
 (0)