Skip to content

Conversation

@jcharkow
Copy link
Collaborator

@jcharkow jcharkow commented Dec 16, 2025

Add a wrapper around the Residue class which is exposed when iterating the amino acids with Py_AASequence

Summary by CodeRabbit

Release Notes

  • New Features

    • Added residue wrapper type providing access to amino acid properties including molecular weight, modifications, and physicochemical characteristics.
    • Sequences now support concatenation with residue objects.
  • Behavior Changes

    • Single-element sequence indexing now returns residue objects instead of sub-sequences.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 16, 2025

Walkthrough

A new Py_Residue wrapper class is introduced for OpenMS amino acid residues with comprehensive accessors and utility methods. The Py_AASequence class is updated to return Py_Residue objects for single-item indexing and to support concatenation with Py_Residue instances. Tests cover both the new class and modified behavior.

Changes

Cohort / File(s) Summary
New Py_Residue wrapper class
openms_python/py_residue.py
Introduces immutable Py_Residue wrapper around OpenMS Residue with properties for name, codes, weight, formula, modifications, neutral losses, pKa values, basicity metrics, and residue-set membership. Includes factory methods from_native() and from_string(), and standard Python special methods (__str__, __repr__, __eq__, __ne__, __hash__).
Public exports
openms_python/__init__.py
Imports and exports Py_Residue in __all__.
Py_AASequence operator updates
openms_python/py_aasequence.py
Adds import of Py_Residue; modifies __getitem__ to return Py_Residue for single indices (via Py_Residue.from_native) instead of Py_AASequence; extends __add__ signature to accept Py_AASequence | str | Py_Residue and appends residue's one-letter code; extends __radd__ to support Py_Residue on left-hand side.
Integration and comprehensive tests
tests/test_py_aasequence.py, tests/test_py_residue.py
Updates existing Py_AASequence tests to use one_letter_code on residue objects and test concatenation with Py_Residue. Adds new test suite for Py_Residue covering construction, properties, modifications, weights, formulas, neutral losses, pKa values, basicity, residue-set membership, equality, and hashing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Breaking API change: __getitem__ with integer index now returns Py_Residue instead of Py_AASequence—verify backward compatibility considerations and all call sites
  • Py_Residue.from_native(): Ensure proper wrapping of all Residue states, including modified residues and edge cases
  • Concatenation operators: Validate __add__ and __radd__ logic for all type combinations (Py_AASequence, str, Py_Residue) and ensure consistent behavior
  • Test coverage: Confirm comprehensive coverage of Py_Residue API surface and updated Py_AASequence behavior

Possibly related PRs

Suggested reviewers

  • timosachsenberg

Poem

🐰 A residue wraps in Pythonic grace,
Where sequences dance with elegant space,
One index, one residue—the contract is clear,
Concatenation flows like lettuce so dear,
Amino acids bloom in the wrapper's embrace! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/py residue' is vague and generic, using non-descriptive terms that don't clearly convey what the changeset accomplishes. Use a more descriptive title like 'Add Py_Residue wrapper class for amino acid residues' that clearly indicates the main change being introduced.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 97.53% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/py_residue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
openms_python/py_aasequence.py (2)

223-253: Docstring and return type annotation need updating for __getitem__.

The docstring (line 233) still states "Returns: Py_AASequence" and the example shows seq[1] # Returns Py_AASequence("E"), but single-index access now returns Py_Residue. The method signature also lacks a return type annotation.

-    def __getitem__(self, index):
+    def __getitem__(self, index: int | slice) -> Py_Residue | Py_AASequence:
         """
         Get residue(s) at position(s).
 
-        Supports both single indexing and slicing, returning Py_AASequence objects.
+        Supports both single indexing and slicing. Single index returns Py_Residue,
+        slicing returns Py_AASequence.
 
         Args:
             index: Integer for single residue, or slice object for subsequence.
 
         Returns:
-            Py_AASequence: Wrapped residue or subsequence.
+            Py_Residue | Py_AASequence: Py_Residue for single index, Py_AASequence for slice.
 
         Example:
             >>> seq = Py_AASequence.from_string("PEPTIDE")
-            >>> seq[1]  # Returns Py_AASequence("E")
+            >>> seq[1]  # Returns Py_Residue for "E"
             >>> seq[1:4]  # Returns Py_AASequence("EPT")
-            >>> seq[-1]  # Returns Py_AASequence("E")
+            >>> seq[-1]  # Returns Py_Residue for "E"
         """

289-305: Update __radd__ type annotation to include Py_Residue.

The method now handles Py_Residue on the left-hand side but the type hint still only shows str.

-    def __radd__(self, other: str) -> Py_AASequence:
+    def __radd__(self, other: str | Py_Residue) -> Py_AASequence:
         """
-        Support string + Py_AASequence.
+        Support string + Py_AASequence and Py_Residue + Py_AASequence.
 
         Example:
             >>> seq = Py_AASequence.from_string("TIDE")
             >>> combined = "PEP" + seq
             >>> print(combined.sequence)
             PEPTIDE
         """
🧹 Nitpick comments (5)
openms_python/py_residue.py (4)

9-38: Well-structured wrapper class with clear documentation.

The class follows the Py_ naming convention and provides a clean docstring with usage examples. The constructor correctly handles the optional native_residue parameter.

However, the docstring claims the wrapper is "immutable" (line 11), but set_modification and set_modification_by_diff_mass methods mutate the underlying residue. Consider updating the docstring to reflect the actual behavior.

 class Py_Residue:
     """
-    A Pythonic, immutable wrapper around pyOpenMS Residue.
+    A Pythonic wrapper around pyOpenMS Residue.
 
     This class provides intuitive properties and methods for working with
     amino acid residues, including access to modifications, formulas,
     weights, and physicochemical properties.

78-89: Improve exception handling with proper chaining.

The static analysis correctly identifies issues here: catching bare Exception and not using exception chaining. This makes debugging harder when the lookup fails.

         # Try direct lookup (works for all formats)
         try:
             residue = db.getResidue(code)
             return cls(residue)
-        except Exception:
-            raise ValueError(f"Residue '{code}' not found in ResidueDB.")
+        except Exception as err:
+            raise ValueError(f"Residue '{code}' not found in ResidueDB.") from err

120-128: Misleading docstring for mono_weight property.

The docstring mentions "Args can be passed" but this is a property with no parameters. The parameterized version is get_mono_weight(). Consider clarifying the docstring.

     @property
     def mono_weight(self) -> float:
         """
-        Get monoisotopic weight.
-
-        Args can be passed to get weight for different residue types
-        (Full, Internal, NTerminal, CTerminal, etc.).
+        Get monoisotopic weight (full residue type).
+
+        For weights with different residue types, use get_mono_weight().
         """
         return self._residue.getMonoWeight()

220-233: Mutation methods don't return self for method chaining.

Per coding guidelines, mutation methods should return self to enable fluent interfaces. This applies to both set_modification and set_modification_by_diff_mass.

Based on coding guidelines, consider returning self for method chaining:

-    def set_modification(self, mod_name: str) -> None:
+    def set_modification(self, mod_name: str) -> Py_Residue:
         """
         Set modification by name.
 
-        Note: This modifies the underlying residue object.
-
         Args:
             mod_name: Name of modification (must exist in ModificationsDB).
 
+        Returns:
+            Py_Residue: Self for method chaining.
+
         Example:
             >>> res = Py_Residue.from_one_letter_code("M")
             >>> res.set_modification("Oxidation")
         """
         self._residue.setModification(mod_name)
+        return self
 
-    def set_modification_by_diff_mass(self, diff_mono_mass: float) -> None:
+    def set_modification_by_diff_mass(self, diff_mono_mass: float) -> Py_Residue:
         """
         Set modification by mass difference.
         ...
         """
         self._residue.setModificationByDiffMonoMass(diff_mono_mass)
+        return self
tests/test_py_residue.py (1)

11-30: Test function contains multiple inline docstrings instead of test separation.

The function test_py_residue_from_string contains three docstrings for what should be separate test cases. This pattern hides test intent and makes it harder to identify which assertion failed.

Consider splitting into separate test functions or using pytest parametrize:

-def test_py_residue_from_string():
-
-    """Test creating residue from one-letter code."""
-    ala = Py_Residue.from_string("A")
-    assert ala.one_letter_code == "A"
-    assert ala.three_letter_code == "Ala"
-    assert ala.name == "Alanine"
-
-    """Test creating residue from three-letter code."""
-    ala = Py_Residue.from_string("Ala")
-    ...
+@pytest.mark.parametrize("code,expected_one,expected_three,expected_name", [
+    ("A", "A", "Ala", "Alanine"),
+    ("Ala", "A", "Ala", "Alanine"),
+    ("Alanine", "A", "Ala", "Alanine"),
+])
+def test_py_residue_from_string(code, expected_one, expected_three, expected_name):
+    """Test creating residue from various code formats."""
+    residue = Py_Residue.from_string(code)
+    assert residue.one_letter_code == expected_one
+    assert residue.three_letter_code == expected_three
+    assert residue.name == expected_name
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 204e8b6 and b3a397e.

📒 Files selected for processing (5)
  • openms_python/__init__.py (2 hunks)
  • openms_python/py_aasequence.py (5 hunks)
  • openms_python/py_residue.py (1 hunks)
  • tests/test_py_aasequence.py (4 hunks)
  • tests/test_py_residue.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Follow PEP 8 conventions
Use Black formatter with 100 character line length (configured in pyproject.toml)
Use type hints for better IDE support and code clarity in Python code
Prefer clear, descriptive names over abbreviations
Add docstrings to all public classes and methods with usage examples

Files:

  • openms_python/__init__.py
  • tests/test_py_residue.py
  • tests/test_py_aasequence.py
  • openms_python/py_residue.py
  • openms_python/py_aasequence.py
tests/test_*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All tests should be in tests/ directory with test_*.py naming convention using pytest as the testing framework

Files:

  • tests/test_py_residue.py
  • tests/test_py_aasequence.py
tests/test_py_*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Test files should include tests for basic wrapper functionality (properties, methods), DataFrame conversions, file I/O, iteration, filtering, and method chaining

Files:

  • tests/test_py_residue.py
  • tests/test_py_aasequence.py
openms_python/py_*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

openms_python/py_*.py: Use @Property decorators instead of verbose get/set methods for wrapper classes
Support Python's iteration protocols (iter, len, getitem) for wrapper classes
Return self from mutation methods to enable method chaining and fluent interfaces
Provide to_dataframe() and from_dataframe() methods for pandas interoperability in wrapper classes
Support with statements for file I/O operations by implementing context managers
Classes wrapping MetaInfoInterface should support dict-like access (getitem, setitem, delitem, contains, iter, len, get(), pop(), update())
Use Py_ prefix for wrapper class names to distinguish them from pyOpenMS classes (e.g., Py_MSExperiment, Py_FeatureMap)
Core wrapper classes should be named with py_*.py file naming convention (e.g., py_msexperiment.py, py_featuremap.py)
Use class methods for alternative constructors like from_file() and from_dataframe() in wrapper classes
Provide multiple ways to filter data through method-based, property-based, and iterator-based approaches
When wrapping pyOpenMS classes, preserve all functionality and add convenience methods without removing or breaking existing capabilities
Provide clear, helpful error messages and validate inputs before passing to pyOpenMS
Minimize wrapper overhead and avoid unnecessary data copies in wrapper implementations
Use NumPy arrays for peak data when possible to optimize performance
Consider performance implications of DataFrame conversions in wrapper classes

Files:

  • openms_python/py_residue.py
  • openms_python/py_aasequence.py
openms_python/{py_*.py,io.py,_io_utils.py,workflows.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Be mindful of memory when working with large datasets and provide streaming alternatives for large files

Files:

  • openms_python/py_residue.py
  • openms_python/py_aasequence.py
openms_python/{py_*.py,io.py,_io_utils.py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Handle common edge cases such as empty containers, missing files, and invalid inputs

Files:

  • openms_python/py_residue.py
  • openms_python/py_aasequence.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : Use Py_ prefix for wrapper class names to distinguish them from pyOpenMS classes (e.g., Py_MSExperiment, Py_FeatureMap)
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : When wrapping pyOpenMS classes, preserve all functionality and add convenience methods without removing or breaking existing capabilities
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Wrapper classes should wrap a corresponding pyOpenMS class and delegate to it while providing Pythonic interfaces
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : Support Python's iteration protocols (__iter__, __len__, __getitem__) for wrapper classes
📚 Learning: 2025-12-02T21:05:09.753Z
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : Use Py_ prefix for wrapper class names to distinguish them from pyOpenMS classes (e.g., Py_MSExperiment, Py_FeatureMap)

Applied to files:

  • openms_python/__init__.py
  • openms_python/py_residue.py
📚 Learning: 2025-12-02T21:05:09.753Z
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : When wrapping pyOpenMS classes, preserve all functionality and add convenience methods without removing or breaking existing capabilities

Applied to files:

  • openms_python/py_residue.py
📚 Learning: 2025-12-02T21:05:09.753Z
Learnt from: CR
Repo: OpenMS/openms-python PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-02T21:05:09.753Z
Learning: Applies to openms_python/py_*.py : Minimize wrapper overhead and avoid unnecessary data copies in wrapper implementations

Applied to files:

  • openms_python/py_residue.py
🧬 Code graph analysis (3)
openms_python/__init__.py (1)
openms_python/py_residue.py (1)
  • Py_Residue (9-403)
tests/test_py_residue.py (2)
openms_python/py_residue.py (35)
  • Py_Residue (9-403)
  • from_string (54-89)
  • one_letter_code (104-106)
  • three_letter_code (109-111)
  • name (99-101)
  • synonyms (114-116)
  • mono_weight (121-128)
  • average_weight (131-133)
  • formula (136-143)
  • is_modified (196-198)
  • modification (201-208)
  • modification_name (211-218)
  • set_modification (220-233)
  • set_modification_by_diff_mass (235-249)
  • has_neutral_loss (254-256)
  • loss_formulas (264-272)
  • loss_names (275-277)
  • has_n_term_neutral_losses (259-261)
  • n_term_loss_formulas (280-288)
  • n_term_loss_names (291-293)
  • low_mass_ions (298-306)
  • pka (311-313)
  • pkb (316-318)
  • pkc (321-323)
  • pi_value (326-328)
  • side_chain_basicity (331-333)
  • backbone_basicity_left (336-338)
  • backbone_basicity_right (341-343)
  • residue_sets (348-357)
  • is_in_residue_set (359-374)
  • get_mono_weight (145-163)
  • get_formula (179-191)
  • get_average_weight (165-177)
  • native (94-96)
  • from_native (41-51)
openms_python/_meta_mapping.py (1)
  • values (121-122)
tests/test_py_aasequence.py (2)
openms_python/py_aasequence.py (3)
  • Py_AASequence (11-434)
  • from_string (43-58)
  • sequence (82-84)
openms_python/py_residue.py (2)
  • one_letter_code (104-106)
  • from_string (54-89)
🪛 GitHub Actions: Integration Tests
tests/test_py_aasequence.py

[warning] 345-345: UserWarning: count method does not account for modifications

🪛 Ruff (0.14.8)
tests/test_py_residue.py

387-387: Do not use bare except

(E722)


387-389: try-except-pass detected, consider logging the exception

(S110)

openms_python/py_residue.py

79-79: Avoid specifying long messages outside the exception class

(TRY003)


88-88: Do not catch blind exception: Exception

(BLE001)


89-89: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

openms_python/py_aasequence.py

251-251: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (8)
openms_python/py_residue.py (1)

388-403: Well-implemented equality and hash methods.

The __eq__ method correctly supports comparison with both Py_Residue and string (one-letter code), which is intuitive for users. The __hash__ implementation uses both one_letter_code and modification_name, ensuring modified residues hash differently. Good implementation aligned with learnings about Pythonic interfaces.

openms_python/__init__.py (1)

33-33: LGTM!

The Py_Residue export follows the established pattern for other wrapper classes and is correctly added to both the import statements and __all__.

Also applies to: 113-113

tests/test_py_residue.py (1)

290-302: Good hashability test coverage.

The test properly validates that Py_Residue instances can be used in sets and as dictionary keys. This aligns with the __hash__ implementation in the wrapper.

tests/test_py_aasequence.py (3)

8-8: LGTM!

Import correctly updated to include Py_Residue from the public API.


116-122: LGTM!

Iteration test correctly updated to access one_letter_code property from the returned Py_Residue objects.


298-327: Comprehensive concatenation test coverage.

Good coverage of all concatenation scenarios: Py_AASequence + Py_AASequence, Py_AASequence + str, str + Py_AASequence, Py_AASequence + Py_Residue, and chaining. The test also verifies immutability by checking original sequences remain unchanged.

openms_python/py_aasequence.py (2)

8-8: LGTM!

Import correctly added for Py_Residue integration.


259-287: LGTM!

The __add__ method correctly handles Py_Residue by extracting its one_letter_code. Type annotation properly updated.

Comment on lines +145 to +163
def get_mono_weight(
self, residue_type: oms.Residue.ResidueType = oms.Residue.ResidueType.Full
) -> float:
"""
Get monoisotopic weight for specific residue type.

Args:
residue_type: Type of residue (Full, Internal, NTerminal, CTerminal,
AIon, BIon, CIon, XIon, YIon, ZIon, etc.).

Returns:
float: Monoisotopic weight.

Example:
>>> res = Py_Residue.from_one_letter_code("A")
>>> full_weight = res.get_mono_weight(oms.Residue.ResidueType.Full)
>>> internal_weight = res.get_mono_weight(oms.Residue.ResidueType.Internal)
"""
return self._residue.getMonoWeight(residue_type)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent example reference in docstring.

The docstring example references Py_Residue.from_one_letter_code("A") but this method doesn't exist. The correct method is from_string().

         Example:
-            >>> res = Py_Residue.from_one_letter_code("A")
+            >>> res = Py_Residue.from_string("A")
             >>> full_weight = res.get_mono_weight(oms.Residue.ResidueType.Full)
             >>> internal_weight = res.get_mono_weight(oms.Residue.ResidueType.Internal)
🤖 Prompt for AI Agents
In openms_python/py_residue.py around lines 145 to 163, the docstring example
calls a nonexistent method Py_Residue.from_one_letter_code("A"); update the
example to call the correct constructor method Py_Residue.from_string("A") (and
update uses of the returned variable if needed), e.g. replace both occurrences
in the Example block so it reads: res = Py_Residue.from_string("A") followed by
calls to res.get_mono_weight(...).

Comment on lines +359 to +374
def is_in_residue_set(self, residue_set: str) -> bool:
"""
Check if residue is in a specific set.

Args:
residue_set: Name of the residue set (e.g., 'Natural20').

Returns:
bool: True if residue is in the set.

Example:
>>> ala = Py_Residue.from_one_letter_code("A")
>>> print(ala.is_in_residue_set("Natural20"))
True
"""
return self._residue.isInResidueSet(residue_set)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Another incorrect example reference.

Same issue as before - references non-existent from_one_letter_code method.

         Example:
-            >>> ala = Py_Residue.from_one_letter_code("A")
+            >>> ala = Py_Residue.from_string("A")
             >>> print(ala.is_in_residue_set("Natural20"))
             True
🤖 Prompt for AI Agents
In openms_python/py_residue.py around lines 359-374, the docstring example
incorrectly references a non-existent from_one_letter_code method; replace that
example with a valid way to construct a Py_Residue (or remove the example).
Specifically, either show using the actual constructor/factory used elsewhere in
the repo (for example Py_Residue('A') if the class accepts a one-letter code) or
remove the example lines entirely so the docstring only documents
is_in_residue_set without calling a non-existent method.

Comment on lines +337 to +354
def test_py_residue_modified_equality():
"""Test equality of modified residues."""
met1 = Py_Residue.from_string("M")
met2 = Py_Residue.from_string("M")

# Before modification, should be equal
assert met1 == met2

# Modify one
met1.set_modification("Oxidation")

# After modification, should not be equal (different modification state)
assert met1 != met2

# Both modified with same modification should be equal
met2.set_modification("Oxidation")
# Note: This might depend on implementation details

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete test for modified residue equality.

The test sets up a comparison between two modified residues but doesn't assert the expected outcome after both are modified with "Oxidation". Add the missing assertion.

     # Both modified with same modification should be equal
     met2.set_modification("Oxidation")
-    # Note: This might depend on implementation details
+    assert met1 == met2, "Residues with same modification should be equal"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_py_residue_modified_equality():
"""Test equality of modified residues."""
met1 = Py_Residue.from_string("M")
met2 = Py_Residue.from_string("M")
# Before modification, should be equal
assert met1 == met2
# Modify one
met1.set_modification("Oxidation")
# After modification, should not be equal (different modification state)
assert met1 != met2
# Both modified with same modification should be equal
met2.set_modification("Oxidation")
# Note: This might depend on implementation details
def test_py_residue_modified_equality():
"""Test equality of modified residues."""
met1 = Py_Residue.from_string("M")
met2 = Py_Residue.from_string("M")
# Before modification, should be equal
assert met1 == met2
# Modify one
met1.set_modification("Oxidation")
# After modification, should not be equal (different modification state)
assert met1 != met2
# Both modified with same modification should be equal
met2.set_modification("Oxidation")
assert met1 == met2, "Residues with same modification should be equal"
🤖 Prompt for AI Agents
In tests/test_py_residue.py around lines 337 to 354, the test sets both residues
to the same modification but never asserts the expected equality; add a final
assertion that met1 and met2 are equal after calling
met2.set_modification("Oxidation") (i.e., assert met1 == met2) so the test
verifies the intended behavior.

Comment on lines +379 to +389
def test_py_residue_special_amino_acids():
"""Test special amino acids like selenocysteine."""
# Note: Availability depends on ResidueDB configuration
# This test might need adjustment based on your OpenMS version
try:
# Try to get selenocysteine (U)
sec = Py_Residue.from_string("U")
assert sec.one_letter_code == "U"
except:
# If not available, that's okay for standard configurations
pass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid bare except clause; log or handle the exception explicitly.

Using bare except silently swallows all exceptions including unexpected ones like KeyboardInterrupt. If the test is meant to handle unavailability gracefully, be explicit about what exception is expected.

 def test_py_residue_special_amino_acids():
     """Test special amino acids like selenocysteine."""
     # Note: Availability depends on ResidueDB configuration
     # This test might need adjustment based on your OpenMS version
     try:
         # Try to get selenocysteine (U)
         sec = Py_Residue.from_string("U")
         assert sec.one_letter_code == "U"
-    except:
-        # If not available, that's okay for standard configurations
-        pass
+    except ValueError:
+        # Selenocysteine may not be available in standard configurations
+        pytest.skip("Selenocysteine (U) not available in ResidueDB")
🧰 Tools
🪛 Ruff (0.14.8)

387-387: Do not use bare except

(E722)


387-389: try-except-pass detected, consider logging the exception

(S110)

🤖 Prompt for AI Agents
In tests/test_py_residue.py around lines 379-389, replace the bare except with
handling for the specific expected error(s) (e.g., KeyError or ValueError) and
surface the reason instead of silently swallowing everything; catch the expected
exception(s) as e and call pytest.skip with a brief explanatory message
including e (or log e), and let any other unexpected exceptions propagate (do
not use a bare except).

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.

2 participants