-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/py residue #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This class is a single amino acid, wrapper around Residue object
WalkthroughA new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 returnsPy_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 includePy_Residue.The method now handles
Py_Residueon the left-hand side but the type hint still only showsstr.- 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 optionalnative_residueparameter.However, the docstring claims the wrapper is "immutable" (line 11), but
set_modificationandset_modification_by_diff_massmethods 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
Exceptionand 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 formono_weightproperty.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 returnselffor method chaining.Per coding guidelines, mutation methods should return
selfto enable fluent interfaces. This applies to bothset_modificationandset_modification_by_diff_mass.Based on coding guidelines, consider returning
selffor 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 selftests/test_py_residue.py (1)
11-30: Test function contains multiple inline docstrings instead of test separation.The function
test_py_residue_from_stringcontains 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
📒 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__.pytests/test_py_residue.pytests/test_py_aasequence.pyopenms_python/py_residue.pyopenms_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.pytests/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.pytests/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.pyopenms_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.pyopenms_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.pyopenms_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__.pyopenms_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 bothPy_Residueand string (one-letter code), which is intuitive for users. The__hash__implementation uses bothone_letter_codeandmodification_name, ensuring modified residues hash differently. Good implementation aligned with learnings about Pythonic interfaces.openms_python/__init__.py (1)
33-33: LGTM!The
Py_Residueexport 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_Residueinstances 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_Residuefrom the public API.
116-122: LGTM!Iteration test correctly updated to access
one_letter_codeproperty from the returnedPy_Residueobjects.
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_Residueintegration.
259-287: LGTM!The
__add__method correctly handlesPy_Residueby extracting itsone_letter_code. Type annotation properly updated.
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(...).
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
Add a wrapper around the
Residueclass which is exposed when iterating the amino acids with Py_AASequenceSummary by CodeRabbit
Release Notes
New Features
Behavior Changes
✏️ Tip: You can customize this high-level summary in your review settings.