Skip to content

Commit ebb5cdf

Browse files
committed
feature: add "+" and slicing for AASequence
1 parent 1707b48 commit ebb5cdf

File tree

1 file changed

+94
-9
lines changed

1 file changed

+94
-9
lines changed

openms_python/py_aasequence.py

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class Py_AASequence:
1010
"""
11-
A Pythonic wrapper around pyOpenMS AASequence.
11+
A Pythonic, immutable wrapper around pyOpenMS AASequence.
1212
1313
This class provides intuitive properties and methods for working with
1414
amino acid sequences, including common operations like reversing and
@@ -204,25 +204,110 @@ def __eq__(self, other: object) -> bool:
204204
return False
205205
return self.sequence == other.sequence
206206

207-
def __getitem__(self, index: int) -> str:
207+
def __getitem__(self, index):
208208
"""
209-
Get residue at position.
209+
Get residue(s) at position(s).
210+
211+
Supports both single indexing and slicing, returning Py_AASequence objects.
210212
211213
Args:
212-
index: Position in the sequence (0-based).
214+
index: Integer for single residue, or slice object for subsequence.
213215
214216
Returns:
215-
str: Single letter amino acid code.
217+
Py_AASequence: Wrapped residue or subsequence.
218+
219+
Example:
220+
>>> seq = Py_AASequence.from_string("PEPTIDE")
221+
>>> seq[1] # Returns Py_AASequence("E")
222+
>>> seq[1:4] # Returns Py_AASequence("EPT")
223+
>>> seq[-1] # Returns Py_AASequence("E")
216224
"""
217-
if index < 0 or index >= len(self):
218-
raise IndexError(f"Index {index} out of range for sequence of length {len(self)}")
219-
residue = self._sequence.getResidue(index)
220-
return residue.getOneLetterCode()
225+
if isinstance(index, slice):
226+
start, stop, step = index.indices(len(self))
227+
if step != 1:
228+
raise ValueError("Step slicing is not supported for amino acid sequences")
229+
subsequence = self.sequence[start:stop]
230+
return Py_AASequence.from_string(subsequence)
231+
else:
232+
# Handle negative indices
233+
if index < 0:
234+
index = len(self) + index
235+
if index < 0 or index >= len(self):
236+
raise IndexError(f"Index {index} out of range for sequence of length {len(self)}")
237+
residue = self._sequence.getResidue(index)
238+
residue_char = residue.getOneLetterCode()
239+
return Py_AASequence.from_string(residue_char)
221240

222241
def __iter__(self):
223242
"""Iterate over residues."""
224243
for i in range(len(self)):
225244
yield self[i]
245+
def __add__(self, other: Py_AASequence | str) -> Py_AASequence:
246+
"""
247+
Concatenate sequences.
248+
249+
Args:
250+
other: Py_AASequence or string to append.
251+
252+
Returns:
253+
Py_AASequence: New concatenated sequence.
254+
255+
Example:
256+
>>> seq1 = Py_AASequence.from_string("PEP")
257+
>>> seq2 = Py_AASequence.from_string("TIDE")
258+
>>> combined = seq1 + seq2
259+
>>> print(combined.sequence)
260+
PEPTIDE
261+
>>> combined2 = seq1 + "TIDE"
262+
>>> print(combined2.sequence)
263+
PEPTIDE
264+
"""
265+
if isinstance(other, Py_AASequence):
266+
combined_str = self.sequence + other.sequence
267+
elif isinstance(other, str):
268+
combined_str = self.sequence + other
269+
else:
270+
return NotImplemented
271+
return Py_AASequence.from_string(combined_str)
272+
273+
def __radd__(self, other: str) -> Py_AASequence:
274+
"""
275+
Support string + Py_AASequence.
276+
277+
Example:
278+
>>> seq = Py_AASequence.from_string("TIDE")
279+
>>> combined = "PEP" + seq
280+
>>> print(combined.sequence)
281+
PEPTIDE
282+
"""
283+
if isinstance(other, str):
284+
combined_str = other + self.sequence
285+
return Py_AASequence.from_string(combined_str)
286+
return NotImplemented
287+
288+
def __mul__(self, times: int) -> Py_AASequence:
289+
"""
290+
Repeat sequence.
291+
292+
Args:
293+
times: Number of times to repeat (must be >= 0).
294+
295+
Returns:
296+
Py_AASequence: New repeated sequence.
297+
298+
Example:
299+
>>> seq = Py_AASequence.from_string("PEP")
300+
>>> repeated = seq * 3
301+
>>> print(repeated.sequence)
302+
PEPPEPPEP
303+
"""
304+
if not isinstance(times, int) or times < 0:
305+
return NotImplemented
306+
return Py_AASequence.from_string(self.sequence * times)
307+
308+
def __rmul__(self, times: int) -> Py_AASequence:
309+
"""Support int * Py_AASequence."""
310+
return self.__mul__(times)
226311

227312
# ==================== Additional Utilities ====================
228313

0 commit comments

Comments
 (0)